diff --git a/packages/kbn-check-saved-objects-cli/current_fields.json b/packages/kbn-check-saved-objects-cli/current_fields.json index 05c098f8f370f..90502ccb33267 100644 --- a/packages/kbn-check-saved-objects-cli/current_fields.json +++ b/packages/kbn-check-saved-objects-cli/current_fields.json @@ -1261,6 +1261,7 @@ ], "spaces-usage-stats": [], "stream-prompts": [], + "streams-significant-events-settings": [], "synthetics-dynamic-settings": [], "synthetics-monitor": [ "alert", diff --git a/packages/kbn-check-saved-objects-cli/current_mappings.json b/packages/kbn-check-saved-objects-cli/current_mappings.json index 2f5843fce21a0..eeca45c3e106b 100644 --- a/packages/kbn-check-saved-objects-cli/current_mappings.json +++ b/packages/kbn-check-saved-objects-cli/current_mappings.json @@ -4135,6 +4135,10 @@ "dynamic": false, "properties": {} }, + "streams-significant-events-settings": { + "dynamic": false, + "properties": {} + }, "synthetics-dynamic-settings": { "dynamic": false, "properties": {} diff --git a/src/core/packages/saved-objects/server-internal/src/object_types/index.ts b/src/core/packages/saved-objects/server-internal/src/object_types/index.ts index 3e9fbeac98b1e..3e98804eab35d 100644 --- a/src/core/packages/saved-objects/server-internal/src/object_types/index.ts +++ b/src/core/packages/saved-objects/server-internal/src/object_types/index.ts @@ -11,4 +11,4 @@ export { registerCoreObjectTypes } from './registration'; // set minimum number of registered saved objects to ensure no object types are removed after 8.8 // declared in internal implementation explicitly to prevent unintended changes. -export const SAVED_OBJECT_TYPES_COUNT = 150 as const; +export const SAVED_OBJECT_TYPES_COUNT = 151 as const; diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 071b90c5a169a..cac764d472608 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -185,6 +185,7 @@ describe('checking migration metadata changes on all registered SO types', () => "space": "a3792f667888f8a976a742c941bd6db28d027604eccc854986a5ec2515f44483", "spaces-usage-stats": "f692e8d75a0ac9187bfbf237e1b5aa6f9a028cf5569a3df2f9e0e96e625b6215", "stream-prompts": "25d761054f4e6c60362ef73da19e1bee4c22bcf79e858bdb11ef3eb7f433c66c", + "streams-significant-events-settings": "698cd5c78851582ab24fa131b9c44be69f1f8c27e79ee0d7cbb07d30d66550e8", "synthetics-dynamic-settings": "30883b9647f119e1d4529f49bc0fee459c56f62489993cd7f2397ffcb8608e63", "synthetics-monitor": "19af677892a5eac45b3d1903c083cb222d8898d69ee545eaf079522b0bbbb85b", "synthetics-monitor-multi-space": "9159e968b4c011ee11fe7887572e883e0928c4b4b0924d672c77003cfdf01fe0", @@ -1211,6 +1212,10 @@ describe('checking migration metadata changes on all registered SO types', () => "stream-prompts|10.2.0: a79e6825ccd534ba98f6b8ce45357da62f162efe3fbad1d0c2c7d3c0f8d1e48c", "stream-prompts|10.1.0: ba35cffdf735265c1b4bc2652c4ea22c74e81fafb58a5c67eba53a4ec3e821bc", "=======================================================================================", + "streams-significant-events-settings|global: 4ed764f62f5d97deb97b50db2c41718ffd13b391", + "streams-significant-events-settings|mappings: e1b10e5bec060a176469a5e9a4f80c94e23abcd7", + "streams-significant-events-settings|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709", + "=====================================================================================", "synthetics-dynamic-settings|global: 79542e96ca9347188221391c3c7157ee5e8daefc", "synthetics-dynamic-settings|mappings: e1b10e5bec060a176469a5e9a4f80c94e23abcd7", "synthetics-dynamic-settings|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709", @@ -1487,6 +1492,7 @@ describe('checking migration metadata changes on all registered SO types', () => "space": "10.2.0", "spaces-usage-stats": "10.0.0", "stream-prompts": "10.3.0", + "streams-significant-events-settings": "10.0.0", "synthetics-dynamic-settings": "10.0.0", "synthetics-monitor": "10.2.0", "synthetics-monitor-multi-space": "10.0.0", @@ -1647,6 +1653,7 @@ describe('checking migration metadata changes on all registered SO types', () => "space": "10.2.0", "spaces-usage-stats": "7.14.1", "stream-prompts": "10.3.0", + "streams-significant-events-settings": "0.0.0", "synthetics-dynamic-settings": "0.0.0", "synthetics-monitor": "10.2.0", "synthetics-monitor-multi-space": "10.0.0", diff --git a/src/core/server/integration_tests/saved_objects/registration/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/registration/type_registrations.test.ts index 1cf564a25fb51..2ce1122a926bb 100644 --- a/src/core/server/integration_tests/saved_objects/registration/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/registration/type_registrations.test.ts @@ -188,6 +188,7 @@ const previouslyRegisteredTypes = [ 'workplace_search_telemetry', 'gap_auto_fill_scheduler', 'trial-companion-nba-milestone', + 'streams-significant-events-settings', ].sort(); describe('SO type registrations', () => { diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts index bec6e381fc5a3..db34a8a88c725 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts @@ -8,7 +8,9 @@ import type { SavedObjectsServiceSetup } from '@kbn/core/server'; import { getStreamsPromptsSavedObject } from './significant_events/prompts_config'; +import { getStreamsSignificantEventsSettingsSavedObject } from './significant_events/model_settings_config'; export const registerStreamsSavedObjects = (savedObjectsService: SavedObjectsServiceSetup) => { savedObjectsService.registerType(getStreamsPromptsSavedObject()); + savedObjectsService.registerType(getStreamsSignificantEventsSettingsSavedObject()); }; diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config.ts new file mode 100644 index 0000000000000..d84c28d15175f --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config.ts @@ -0,0 +1,40 @@ +/* + * 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 { SavedObjectsType } from '@kbn/core/server'; +import { schema, type TypeOf } from '@kbn/config-schema'; + +export const STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SO_TYPE = 'streams-significant-events-settings'; + +export const streamsSignificantEventsSettingsSOAttributesV1 = schema.object({ + connectorIdKnowledgeIndicatorExtraction: schema.maybe(schema.string()), + connectorIdRuleGeneration: schema.maybe(schema.string()), + connectorIdDiscovery: schema.maybe(schema.string()), +}); + +export type ModelSettingsConfigAttributes = TypeOf< + typeof streamsSignificantEventsSettingsSOAttributesV1 +>; + +const SINGLETON_ID = 'streams-significant-events-settings'; + +export const getStreamsSignificantEventsSettingsSavedObject = (): SavedObjectsType => { + return { + name: STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SO_TYPE, + hidden: false, + namespaceType: 'multiple', + mappings: { + dynamic: false, + properties: {}, + }, + management: { + importableAndExportable: true, + }, + }; +}; + +export const STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SINGLETON_ID = SINGLETON_ID; diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config_client.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config_client.ts new file mode 100644 index 0000000000000..87d6b5116f053 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config_client.ts @@ -0,0 +1,94 @@ +/* + * 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 { Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import { + STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SO_TYPE, + STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SINGLETON_ID, +} from './model_settings_config'; +import type { ModelSettingsConfigAttributes } from './model_settings_config'; + +/** + * Raw model settings as stored or returned by the API. + * Each property is undefined when no saved object exists or the value was never set. + */ +export interface ModelSettings { + connectorIdKnowledgeIndicatorExtraction?: string; + connectorIdRuleGeneration?: string; + connectorIdDiscovery?: string; +} + +export interface ModelSettingsConfigClient { + getSettings(): Promise; + updateSettings(settings: Partial): Promise; +} + +export class ModelSettingsConfigClientImpl implements ModelSettingsConfigClient { + constructor( + private readonly soClient: SavedObjectsClientContract, + private readonly logger: Logger + ) {} + + async getSettings(): Promise { + let attributes: ModelSettingsConfigAttributes | null = null; + try { + const data = await this.soClient.get( + STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SO_TYPE, + STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SINGLETON_ID + ); + attributes = data.attributes; + } catch (err) { + if ( + (err as { output?: { statusCode?: number } })?.output?.statusCode === 404 || + (err as { statusCode?: number })?.statusCode === 404 + ) { + this.logger.debug( + `No saved settings found for ${STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SINGLETON_ID}` + ); + } else { + throw err; + } + } + + if (!attributes) { + return { + connectorIdKnowledgeIndicatorExtraction: undefined, + connectorIdRuleGeneration: undefined, + connectorIdDiscovery: undefined, + }; + } + + const toOptional = (v: string | undefined) => (v != null && v.trim() !== '' ? v : undefined); + return { + connectorIdKnowledgeIndicatorExtraction: toOptional( + attributes.connectorIdKnowledgeIndicatorExtraction + ), + connectorIdRuleGeneration: toOptional(attributes.connectorIdRuleGeneration), + connectorIdDiscovery: toOptional(attributes.connectorIdDiscovery), + }; + } + + async updateSettings(settings: Partial): Promise { + const current = await this.getSettings(); + const updates = Object.fromEntries( + Object.entries(settings).filter(([, v]) => v !== undefined) + ) as Partial; + const merged: ModelSettings = { ...current, ...updates }; + const toWrite = Object.fromEntries( + Object.entries(merged).filter(([, v]) => v !== undefined) + ) as ModelSettingsConfigAttributes; + + await this.soClient.create( + STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SO_TYPE, + toWrite, + { + id: STREAMS_SIGNIFICANT_EVENTS_SETTINGS_SINGLETON_ID, + overwrite: true, + } + ); + } +} diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config_service.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config_service.ts new file mode 100644 index 0000000000000..73e00bff99be4 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/model_settings_config_service.ts @@ -0,0 +1,21 @@ +/* + * 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 { Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import type { ModelSettingsConfigClient } from './model_settings_config_client'; +import { ModelSettingsConfigClientImpl } from './model_settings_config_client'; + +export type { ModelSettings, ModelSettingsConfigClient } from './model_settings_config_client'; + +export class ModelSettingsConfigService { + constructor(private readonly logger: Logger) {} + + getClient({ soClient }: { soClient: SavedObjectsClientContract }): ModelSettingsConfigClient { + const clientLogger = this.logger.get('model-settings-config-client'); + return new ModelSettingsConfigClientImpl(soClient, clientLogger); + } +} diff --git a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification.ts b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification.ts index 61eb0b74f343b..124e05a189417 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification.ts @@ -20,6 +20,7 @@ import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { LogMeta } from '@kbn/logging'; import { getErrorMessage } from '../../streams/errors/parse_error'; import { formatInferenceProviderError } from '../../../routes/utils/create_connector_sse_error'; +import { resolveConnectorId } from '../../../routes/utils/resolve_connector_id'; import type { TaskContext } from '.'; import type { TaskParams } from '../types'; import { PromptsConfigService } from '../../saved_objects/significant_events/prompts_config_service'; @@ -29,7 +30,6 @@ import { isDefinitionNotFoundError } from '../../streams/errors/definition_not_f import type { StreamsFeaturesIdentifiedProps } from '../../telemetry'; export interface FeaturesIdentificationTaskParams { - connectorId: string; start: number; end: number; streamName: string; @@ -53,7 +53,7 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext } const taskStart = Date.now(); - const { connectorId, start, end, streamName, _task } = runContext.taskInstance + const { start, end, streamName, _task } = runContext.taskInstance .params as TaskParams; const telemetryProps: StreamsFeaturesIdentifiedProps = { @@ -76,10 +76,21 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext streamsClient, inferenceClient, soClient, + modelSettingsClient, + uiSettingsClient, } = await taskContext.getScopedClients({ request: runContext.fakeRequest, }); + const taskLogger = taskContext.logger.get('features_identification'); + const settings = await modelSettingsClient.getSettings(); + const connectorId = await resolveConnectorId({ + connectorId: settings.connectorIdKnowledgeIndicatorExtraction, + uiSettingsClient, + logger: taskLogger, + }); + taskLogger.debug(`Using connector ${connectorId} for knowledge indicator extraction`); + try { const [stream, { featurePromptOverride }] = await Promise.all([ streamsClient.getStream(streamName), @@ -183,7 +194,7 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext await taskClient.complete( _task, - { connectorId, start, end, streamName }, + { start, end, streamName }, { features } ); @@ -232,7 +243,7 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext await taskClient.fail( _task, - { connectorId, start, end, streamName }, + { start, end, streamName }, errorMessage ); diff --git a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/insights_discovery.ts b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/insights_discovery.ts index 9e5088e5b5b3b..9d577f47ff5b8 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/insights_discovery.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/insights_discovery.ts @@ -18,6 +18,7 @@ import type { TaskParams } from '../types'; import { generateInsights } from '../../significant_events/insights/generate_insights'; import { getErrorMessage } from '../../streams/errors/parse_error'; import { formatInferenceProviderError } from '../../../routes/utils/create_connector_sse_error'; +import { resolveConnectorId } from '../../../routes/utils/resolve_connector_id'; export interface InsightsDiscoveryTaskResult { insights: Insight[]; @@ -25,7 +26,6 @@ export interface InsightsDiscoveryTaskResult { } export interface InsightsDiscoveryTaskParams { - connectorId: string; /** When provided, only generate insights for these stream names. Otherwise all streams are used. */ streamNames?: string[]; } @@ -43,7 +43,7 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { throw new Error('Request is required to run this task'); } - const { connectorId, streamNames, _task } = runContext.taskInstance + const { streamNames, _task } = runContext.taskInstance .params as TaskParams; const { @@ -53,10 +53,20 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { inferenceClient, queryClient, insightClient, + modelSettingsClient, + uiSettingsClient, } = await taskContext.getScopedClients({ request: runContext.fakeRequest, }); + const taskLogger = taskContext.logger.get('insights_discovery'); + const settings = await modelSettingsClient.getSettings(); + const connectorId = await resolveConnectorId({ + connectorId: settings.connectorIdDiscovery, + uiSettingsClient, + logger: taskLogger, + }); + taskLogger.debug(`Using connector ${connectorId} for discovery`); const boundInferenceClient = inferenceClient.bindTo({ connectorId }); try { @@ -66,7 +76,7 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { esClient: scopedClusterClient.asCurrentUser, inferenceClient: boundInferenceClient, signal: runContext.abortController.signal, - logger: taskContext.logger.get('insights_discovery'), + logger: taskLogger, streamNames, }); @@ -104,7 +114,7 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { await taskClient.complete( _task, - { connectorId, streamNames }, + { streamNames }, { insights, tokensUsed: result.tokens_used } ); } catch (error) { @@ -128,7 +138,7 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { await taskClient.fail( _task, - { connectorId, streamNames }, + { streamNames }, errorMessage ); return getDeleteTaskRunResult(); diff --git a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/onboarding.ts b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/onboarding.ts index 5c47cec7e53bd..808514fa54734 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/onboarding.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/onboarding.ts @@ -22,6 +22,7 @@ import type { LogMeta } from '@kbn/logging'; import type { StreamsTaskType, TaskContext } from '.'; import { getErrorMessage } from '../../streams/errors/parse_error'; import { formatInferenceProviderError } from '../../../routes/utils/create_connector_sse_error'; +import { resolveConnectorId } from '../../../routes/utils/resolve_connector_id'; import type { QueryClient } from '../../streams/assets/query/query_client'; import type { StreamsClient } from '../../streams/client'; import { cancellableTask } from '../cancellable_task'; @@ -39,7 +40,6 @@ import { } from './significant_events_queries_generation'; export interface OnboardingTaskParams { - connectorId: string; streamName: string; from: number; to: number; @@ -66,13 +66,19 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { throw new Error('Request is required to run this task'); } - const { connectorId, streamName, from, to, steps, saveQueries, _task } = runContext - .taskInstance.params as TaskParams; + const { streamName, from, to, steps, saveQueries, _task } = runContext.taskInstance + .params as TaskParams; - const { taskClient, inferenceClient, queryClient, streamsClient } = - await taskContext.getScopedClients({ - request: runContext.fakeRequest, - }); + const { + taskClient, + inferenceClient, + queryClient, + streamsClient, + modelSettingsClient, + uiSettingsClient, + } = await taskContext.getScopedClients({ + request: runContext.fakeRequest, + }); try { let featuresTaskResult: TaskResult | undefined; @@ -85,7 +91,6 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { case OnboardingStep.FeaturesIdentification: const featuresTaskId = await scheduleFeaturesIdentificationTask( { - connectorId, start: from, end: to, streamName, @@ -107,7 +112,6 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { case OnboardingStep.QueriesGeneration: const queriesTaskId = await scheduleQueriesGenerationTask( { - connectorId, start: from, end: to, streamName, @@ -140,16 +144,30 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { await taskClient.complete( _task, - { connectorId, streamName, from, to, steps, saveQueries }, + { streamName, from, to, steps, saveQueries }, { featuresTaskResult, queriesTaskResult } ); } catch (error) { - // Get connector info for error enrichment - const connector = await inferenceClient.getConnectorById(connectorId); - - const errorMessage = isInferenceProviderError(error) - ? formatInferenceProviderError(error, connector) - : getErrorMessage(error); + // Get connector info for error enrichment (use rule generation connector; fallback to default) + let errorMessage = getErrorMessage(error); + try { + const onboardingLogger = taskContext.logger.get('onboarding'); + const settings = await modelSettingsClient.getSettings(); + const connectorIdForError = await resolveConnectorId({ + connectorId: settings.connectorIdRuleGeneration, + uiSettingsClient, + logger: onboardingLogger, + }); + onboardingLogger.debug( + `Using connector ${connectorIdForError} for rule generation (error enrichment)` + ); + const connector = await inferenceClient.getConnectorById(connectorIdForError); + if (isInferenceProviderError(error)) { + errorMessage = formatInferenceProviderError(error, connector); + } + } catch { + // Use generic error message if we cannot resolve the connector + } if ( errorMessage.includes('ERR_CANCELED') || @@ -166,7 +184,6 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { await taskClient.fail( _task, { - connectorId, streamName, from, to, diff --git a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/significant_events_queries_generation.ts b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/significant_events_queries_generation.ts index 733bac3bd04bd..94fd17d0a1009 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/significant_events_queries_generation.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/significant_events_queries_generation.ts @@ -14,6 +14,7 @@ import { import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import { getErrorMessage } from '../../streams/errors/parse_error'; import { formatInferenceProviderError } from '../../../routes/utils/create_connector_sse_error'; +import { resolveConnectorId } from '../../../routes/utils/resolve_connector_id'; import type { TaskContext } from '.'; import type { TaskParams } from '../types'; import { PromptsConfigService } from '../../saved_objects/significant_events/prompts_config_service'; @@ -22,7 +23,6 @@ import { generateSignificantEventDefinitions } from '../../significant_events/ge import { isDefinitionNotFoundError } from '../../streams/errors/definition_not_found_error'; export interface SignificantEventsQueriesGenerationTaskParams { - connectorId: string; start: number; end: number; sampleDocsSize?: number; @@ -47,8 +47,8 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: throw new Error('Request is required to run this task'); } - const { connectorId, start, end, sampleDocsSize, streamName, _task } = runContext - .taskInstance.params as TaskParams; + const { start, end, sampleDocsSize, streamName, _task } = runContext.taskInstance + .params as TaskParams; const { taskClient, @@ -57,10 +57,21 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: soClient, featureClient, scopedClusterClient, + modelSettingsClient, + uiSettingsClient, } = await taskContext.getScopedClients({ request: runContext.fakeRequest, }); + const taskLogger = taskContext.logger.get('significant_events_queries_generation'); + const settings = await modelSettingsClient.getSettings(); + const connectorId = await resolveConnectorId({ + connectorId: settings.connectorIdRuleGeneration, + uiSettingsClient, + logger: taskLogger, + }); + taskLogger.debug(`Using connector ${connectorId} for rule generation`); + try { const stream = await streamsClient.getStream(streamName); @@ -102,7 +113,7 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: await taskClient.complete< SignificantEventsQueriesGenerationTaskParams, SignificantEventsQueriesGenerationResult - >(_task, { connectorId, start, end, sampleDocsSize, streamName }, result); + >(_task, { start, end, sampleDocsSize, streamName }, result); } catch (error) { if (isDefinitionNotFoundError(error)) { taskContext.logger.debug( @@ -131,7 +142,7 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: await taskClient.fail( _task, - { connectorId, start, end, sampleDocsSize, streamName }, + { start, end, sampleDocsSize, streamName }, errorMessage ); diff --git a/x-pack/platform/plugins/shared/streams/server/plugin.ts b/x-pack/platform/plugins/shared/streams/server/plugin.ts index a9fa0441bc1a3..62b9dfdb22346 100644 --- a/x-pack/platform/plugins/shared/streams/server/plugin.ts +++ b/x-pack/platform/plugins/shared/streams/server/plugin.ts @@ -50,6 +50,7 @@ import { backfillWiredStreamViews } from './lib/streams/esql_views/backfill_wire import { FeatureService } from './lib/streams/feature/feature_service'; import { ProcessorSuggestionsService } from './lib/streams/ingest_pipelines/processor_suggestions_service'; import { registerStreamsSavedObjects } from './lib/saved_objects/register_saved_objects'; +import { ModelSettingsConfigService } from './lib/saved_objects/significant_events/model_settings_config_service'; import { TaskService } from './lib/tasks/task_service'; import { InsightService } from './lib/significant_events/insights/client/insight_service'; import { baseFields } from './lib/streams/component_templates/logs_layer'; @@ -120,6 +121,7 @@ export class StreamsPlugin const contentService = new ContentService(core, this.logger); const queryService = new QueryService(core, this.logger); const taskService = new TaskService(plugins.taskManager); + const modelSettingsConfigService = new ModelSettingsConfigService(this.logger); const getScopedClients = async ({ request, @@ -170,6 +172,10 @@ export class StreamsPlugin uiSettingsClient, }); + const modelSettingsClient = modelSettingsConfigService.getClient({ + soClient, + }); + return { scopedClusterClient, soClient, @@ -184,6 +190,7 @@ export class StreamsPlugin licensing, uiSettingsClient, taskClient, + modelSettingsClient, }; }; diff --git a/x-pack/platform/plugins/shared/streams/server/routes/index.ts b/x-pack/platform/plugins/shared/streams/server/routes/index.ts index 7e1fa6919718f..11bbe645b1f95 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/index.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/index.ts @@ -18,6 +18,7 @@ import { internalCrudRoutes } from './internal/streams/crud/route'; import { internalManagementRoutes } from './internal/streams/management/route'; import { internalPromptsRoutes } from './internal/streams/prompts/route'; import { internalSignificantEventsRoutes } from './internal/streams/significant_events/route'; +import { internalSignificantEventsSettingsRoutes } from './internal/streams/significant_events_settings/route'; import { significantEventsRoutes } from './streams/significant_events/route'; import { queryRoutes } from './queries/route'; import { failureStoreRoutes } from './internal/streams/failure_store/route'; @@ -45,6 +46,7 @@ export const streamsRouteRepository = { ...timeSeriesRoutes, ...internalPromptsRoutes, ...internalSignificantEventsRoutes, + ...internalSignificantEventsSettingsRoutes, ...internalIngestRoutes, ...connectorRoutes, ...internalAttachmentRoutes, diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts index 21c9b9a14bc3d..647fcf4b44315 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts @@ -12,7 +12,6 @@ import { v4 as uuid } from 'uuid'; import { createServerRoute } from '../../../create_server_route'; import { assertSignificantEventsAccess } from '../../../utils/assert_significant_events_access'; import { STREAMS_API_PRIVILEGES } from '../../../../../common/constants'; -import { resolveConnectorId } from '../../../utils/resolve_connector_id'; import { type FeaturesIdentificationTaskParams, getFeaturesIdentificationTaskId, @@ -289,12 +288,6 @@ export const featuresTaskRoute = createServerRoute({ body: taskActionSchema({ from: dateFromString, to: dateFromString, - connector_id: z - .string() - .optional() - .describe( - 'Optional connector ID. If not provided, the default AI connector from settings will be used.' - ), }), }), handler: async ({ @@ -302,7 +295,6 @@ export const featuresTaskRoute = createServerRoute({ request, getScopedClients, server, - logger, }): Promise => { const { streamsClient, licensing, uiSettingsClient, taskClient } = await getScopedClients({ request, @@ -325,19 +317,11 @@ export const featuresTaskRoute = createServerRoute({ scheduleConfig: { taskType: FEATURES_IDENTIFICATION_TASK_TYPE, taskId, - params: await (async (): Promise => { - const connectorId = await resolveConnectorId({ - connectorId: body.connector_id, - uiSettingsClient, - logger, - }); - return { - connectorId, - start: body.from.getTime(), - end: body.to.getTime(), - streamName: name, - }; - })(), + params: { + start: body.from.getTime(), + end: body.to.getTime(), + streamName: name, + }, request, }, } as const) diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/insights/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/insights/route.ts index 78ab84f42f1be..5b6fbbc1241a2 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/insights/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/insights/route.ts @@ -23,7 +23,6 @@ import { STREAMS_INSIGHTS_DISCOVERY_TASK_TYPE } from '../../../../lib/tasks/task import { taskActionSchema } from '../../../../lib/tasks/task_action_schema'; import { createServerRoute } from '../../../create_server_route'; import { assertSignificantEventsAccess } from '../../../utils/assert_significant_events_access'; -import { resolveConnectorId } from '../../../utils/resolve_connector_id'; import { handleTaskAction } from '../../../utils/task_helpers'; /* Insights Discovery Task */ @@ -44,25 +43,13 @@ const insightsTaskRoute = createServerRoute({ }, params: z.object({ body: taskActionSchema({ - connectorId: z - .string() - .optional() - .describe( - 'Optional connector ID. If not provided, the default AI connector from settings will be used.' - ), streamNames: z .array(z.string()) .describe('List of stream names to generate insights for.') .optional(), }), }), - handler: async ({ - params, - request, - getScopedClients, - server, - logger, - }): Promise => { + handler: async ({ params, request, getScopedClients, server }): Promise => { const { licensing, uiSettingsClient, taskClient } = await getScopedClients({ request, }); @@ -78,18 +65,9 @@ const insightsTaskRoute = createServerRoute({ scheduleConfig: { taskType: STREAMS_INSIGHTS_DISCOVERY_TASK_TYPE, taskId: STREAMS_INSIGHTS_DISCOVERY_TASK_TYPE, - params: await (async (): Promise => { - const connectorId = await resolveConnectorId({ - connectorId: body.connectorId, - uiSettingsClient, - logger, - }); - - return { - connectorId, - streamNames: body.streamNames, - }; - })(), + params: { + streamNames: body.streamNames, + }, request, }, } as const) diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/onboarding/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/onboarding/route.ts index 21e45723e12bb..23d926a9ddd4d 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/onboarding/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/onboarding/route.ts @@ -17,7 +17,6 @@ import { } from '../../../../lib/tasks/task_definitions/onboarding'; import { createServerRoute } from '../../../create_server_route'; import { assertSignificantEventsAccess } from '../../../utils/assert_significant_events_access'; -import { resolveConnectorId } from '../../../utils/resolve_connector_id'; import { handleTaskAction } from '../../../utils/task_helpers'; import { taskActionSchema } from '../../../../lib/tasks/task_action_schema'; @@ -47,12 +46,6 @@ export const onboardingTaskRoute = createServerRoute({ body: taskActionSchema({ from: timestampFromString, to: timestampFromString, - connectorId: z - .string() - .optional() - .describe( - 'Optional connector ID. If not provided, the default AI connector from settings will be used.' - ), steps: z .array(z.nativeEnum(OnboardingStep)) .optional() @@ -62,13 +55,7 @@ export const onboardingTaskRoute = createServerRoute({ ), }), }), - handler: async ({ - params, - request, - getScopedClients, - server, - logger, - }): Promise => { + handler: async ({ params, request, getScopedClients, server }): Promise => { const { licensing, uiSettingsClient, taskClient } = await getScopedClients({ request, }); @@ -92,22 +79,13 @@ export const onboardingTaskRoute = createServerRoute({ scheduleConfig: { taskType: STREAMS_ONBOARDING_TASK_TYPE, taskId: onboardingTaskId, - params: await (async (): Promise => { - const connectorId = await resolveConnectorId({ - connectorId: body.connectorId, - uiSettingsClient, - logger, - }); - - return { - connectorId, - streamName, - from: body.from, - to: body.to, - steps: body.steps, - saveQueries, - }; - })(), + params: { + streamName, + from: body.from, + to: body.to, + steps: body.steps, + saveQueries, + }, request, }, } as const) diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events/route.ts index ee5d1449cd3c5..96b809eec2eb9 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events/route.ts @@ -22,7 +22,6 @@ import { taskActionSchema } from '../../../../lib/tasks/task_action_schema'; import { createServerRoute } from '../../../create_server_route'; import { assertSignificantEventsAccess } from '../../../utils/assert_significant_events_access'; import { handleTaskAction } from '../../../utils/task_helpers'; -import { resolveConnectorId } from '../../../utils/resolve_connector_id'; // Make sure strings are expected for input, but still converted to a // Date, without breaking the OpenAPI generator @@ -88,12 +87,6 @@ const significantEventsQueriesGenerationTaskRoute = createServerRoute({ body: taskActionSchema({ from: dateFromString.describe('Start of the time range'), to: dateFromString.describe('End of the time range'), - connectorId: z - .string() - .optional() - .describe( - 'Optional connector ID. If not provided, the default AI connector from settings will be used.' - ), }), }), options: { @@ -112,7 +105,6 @@ const significantEventsQueriesGenerationTaskRoute = createServerRoute({ request, getScopedClients, server, - logger, }): Promise => { const { streamsClient, licensing, uiSettingsClient, taskClient } = await getScopedClients({ request, @@ -132,19 +124,11 @@ const significantEventsQueriesGenerationTaskRoute = createServerRoute({ scheduleConfig: { taskType: SIGNIFICANT_EVENTS_QUERIES_GENERATION_TASK_TYPE, taskId, - params: await (async (): Promise => { - const connectorId = await resolveConnectorId({ - connectorId: body.connectorId, - uiSettingsClient, - logger, - }); - return { - connectorId, - start: body.from.getTime(), - end: body.to.getTime(), - streamName: name, - }; - })(), + params: { + start: body.from.getTime(), + end: body.to.getTime(), + streamName: name, + }, request, }, } as const) diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events_settings/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events_settings/route.ts new file mode 100644 index 0000000000000..c0663dd943c7a --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/significant_events_settings/route.ts @@ -0,0 +1,70 @@ +/* + * 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 { z } from '@kbn/zod/v4'; +import type { ModelSettings } from '../../../../lib/saved_objects/significant_events/model_settings_config_service'; +import { createServerRoute } from '../../../create_server_route'; +import { assertSignificantEventsAccess } from '../../../utils/assert_significant_events_access'; +import { STREAMS_API_PRIVILEGES } from '../../../../../common/constants'; + +export const getSignificantEventsSettingsRoute = createServerRoute({ + endpoint: 'GET /internal/streams/_significant_events/settings', + options: { + access: 'internal', + summary: 'Get settings for Significant Events', + description: 'Returns the settings defined by the user for the Significant Events feature.', + }, + security: { + authz: { + requiredPrivileges: [STREAMS_API_PRIVILEGES.read], + }, + }, + handler: async ({ request, getScopedClients, server }): Promise => { + const { modelSettingsClient, licensing, uiSettingsClient } = await getScopedClients({ + request, + }); + await assertSignificantEventsAccess({ server, licensing, uiSettingsClient }); + return modelSettingsClient.getSettings(); + }, +}); + +const putSignificantEventsSettingsBodySchema = z.object({ + connectorIdKnowledgeIndicatorExtraction: z.string().optional(), + connectorIdRuleGeneration: z.string().optional(), + connectorIdDiscovery: z.string().optional(), +}); + +export const putSignificantEventsSettingsRoute = createServerRoute({ + endpoint: 'PUT /internal/streams/_significant_events/settings', + options: { + access: 'internal', + summary: 'Update significant events settings', + description: + 'Sets one or more significant events settings. Omitted fields are left unchanged. Currently supports model settings (connector IDs): use empty string to use the default connector for that setting. Additional settings may be supported in the future.', + }, + security: { + authz: { + requiredPrivileges: [STREAMS_API_PRIVILEGES.manage], + }, + }, + params: z.object({ + body: putSignificantEventsSettingsBodySchema, + }), + handler: async ({ params, request, getScopedClients, server }): Promise<{ success: true }> => { + const { modelSettingsClient, licensing, uiSettingsClient } = await getScopedClients({ + request, + }); + await assertSignificantEventsAccess({ server, licensing, uiSettingsClient }); + await modelSettingsClient.updateSettings(params.body); + return { success: true }; + }, +}); + +export const internalSignificantEventsSettingsRoutes = { + ...getSignificantEventsSettingsRoute, + ...putSignificantEventsSettingsRoute, +}; diff --git a/x-pack/platform/plugins/shared/streams/server/routes/types.ts b/x-pack/platform/plugins/shared/streams/server/routes/types.ts index 5f1871df4289f..92880cc715abb 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/types.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/types.ts @@ -24,6 +24,7 @@ import type { ProcessorSuggestionsService } from '../lib/streams/ingest_pipeline import type { TaskClient } from '../lib/tasks/task_client'; import type { StreamsTaskType } from '../lib/tasks/task_definitions'; import type { InsightClient } from '../lib/significant_events/insights/client/insight_client'; +import type { ModelSettingsConfigClient } from '../lib/saved_objects/significant_events/model_settings_config_service'; export type GetScopedClients = ({ request, @@ -45,6 +46,7 @@ export interface RouteHandlerScopedClients { uiSettingsClient: IUiSettingsClient; fieldsMetadataClient: IFieldsMetadataClient; taskClient: TaskClient; + modelSettingsClient: ModelSettingsConfigClient; } export interface RouteDependencies { diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/insights/summary.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/insights/summary.tsx index e2460992656ac..d68cd5918274d 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/insights/summary.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/insights/summary.tsx @@ -19,17 +19,14 @@ import { TaskStatus } from '@kbn/streams-schema'; import React, { useEffect, useRef, useState } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; import type { Insight } from '@kbn/streams-schema'; -import { useAIFeatures } from '../../../../hooks/use_ai_features'; import { useInsightsDiscoveryApi } from '../../../../hooks/use_insights_discovery_api'; import { useKibana } from '../../../../hooks/use_kibana'; import { useTaskPolling } from '../../../../hooks/use_task_polling'; import { getFormattedError } from '../../../../util/errors'; -import { ConnectorListButton } from '../../../connector_list_button/connector_list_button'; import { FeedbackButtons } from './feedback_buttons'; import { InsightCard } from './insight_card'; export function Summary({ count }: { count: number }) { - const aiFeatures = useAIFeatures(); const { core: { notifications }, } = useKibana(); @@ -39,7 +36,7 @@ export function Summary({ count }: { count: number }) { getInsightsDiscoveryTaskStatus, acknowledgeInsightsDiscoveryTask, cancelInsightsDiscoveryTask, - } = useInsightsDiscoveryApi(aiFeatures?.genAiConnectors.selectedConnector); + } = useInsightsDiscoveryApi(); const [{ value: task }, getTaskStatus] = useAsyncFn(getInsightsDiscoveryTaskStatus); const [{ loading: isSchedulingTask }, scheduleTask] = useAsyncFn(async () => { @@ -192,25 +189,23 @@ export function Summary({ count }: { count: number }) { - + + {task?.status === TaskStatus.InProgress + ? i18n.translate('xpack.streams.insights.generatingButtonLabel', { + defaultMessage: 'Discovering Significant Events', + }) + : i18n.translate('xpack.streams.insights.generateButtonLabel', { + defaultMessage: 'Discover Significant Events', + })} + {(task?.status === TaskStatus.InProgress || isCancellingTask) && ( + streams.streamsRepositoryClient.fetch('GET /internal/streams/_significant_events/settings', { + signal, + }), + [streams.streamsRepositoryClient] + ); + + const [knowledgeIndicatorExtraction, setKnowledgeIndicatorExtraction] = useState(''); + const [ruleGeneration, setRuleGeneration] = useState(''); + const [discovery, setDiscovery] = useState(''); + const [isSaving, setIsSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + // Initialize form from API (undefined or '' = not set, string = connector id) + useEffect(() => { + if (!settingsFetch.value) return; + const v = settingsFetch.value; + setKnowledgeIndicatorExtraction(toFormValue(v.connectorIdKnowledgeIndicatorExtraction)); + setRuleGeneration(toFormValue(v.connectorIdRuleGeneration)); + setDiscovery(toFormValue(v.connectorIdDiscovery)); + }, [settingsFetch.value]); + + const handleSave = useCallback(async () => { + setSaveError(null); + setIsSaving(true); + try { + await streams.streamsRepositoryClient.fetch( + 'PUT /internal/streams/_significant_events/settings', + { + signal: abortSignal, + params: { + body: { + connectorIdKnowledgeIndicatorExtraction: knowledgeIndicatorExtraction, + connectorIdRuleGeneration: ruleGeneration, + connectorIdDiscovery: discovery, + }, + }, + } + ); + settingsFetch.refresh(); + } catch (err) { + setSaveError(err instanceof Error ? err : new Error(String(err))); + } finally { + setIsSaving(false); + } + }, [ + streams.streamsRepositoryClient, + abortSignal, + knowledgeIndicatorExtraction, + ruleGeneration, + discovery, + settingsFetch, + ]); + + const connectorOptions = [ + { + value: NOT_SET_VALUE, + text: i18n.translate('xpack.streams.significantEventsDiscovery.settings.useDefaultOption', { + defaultMessage: 'Use default (genAiSettings:defaultAIConnector)', + }), + }, + ...(genAiConnectors.connectors ?? []).map((c) => ({ value: c.id, text: c.name })), + ]; + + if (settingsFetch.loading && !settingsFetch.value) { + return ; + } + + const hasDefaultConnector = Boolean(genAiConnectors.defaultConnector); + const anyUsesDefault = + knowledgeIndicatorExtraction === NOT_SET_VALUE || + ruleGeneration === NOT_SET_VALUE || + discovery === NOT_SET_VALUE; + const showNoDefaultCallout = !genAiConnectors.loading && !hasDefaultConnector && anyUsesDefault; + const defaultConnectorName = + hasDefaultConnector && anyUsesDefault + ? genAiConnectors.connectors?.find((c) => c.id === genAiConnectors.defaultConnector)?.name + : undefined; + + return ( + + +

+ {i18n.translate('xpack.streams.significantEventsDiscovery.settings.title', { + defaultMessage: 'Model selection', + })} +

+
+ {defaultConnectorName && ( + <> + +

+ {i18n.translate( + 'xpack.streams.significantEventsDiscovery.settings.defaultConnectorLabel', + { + defaultMessage: 'Default connector: {name}', + values: { name: defaultConnectorName }, + } + )} +

+ + )} + + {showNoDefaultCallout && ( + <> + +

+ {i18n.translate( + 'xpack.streams.significantEventsDiscovery.settings.noDefaultConnectorDescription', + { + defaultMessage: + 'Processes that use "Use default" require a default connector. Open GenAI Settings to configure one.', + } + )}{' '} + + {i18n.translate( + 'xpack.streams.significantEventsDiscovery.settings.genAiSettingsLink', + { defaultMessage: 'Open GenAI Settings' } + )} + +

+
+ + + )} + + + { + e.preventDefault(); + handleSave(); + }} + > + + setKnowledgeIndicatorExtraction(e.target.value)} + isLoading={genAiConnectors.loading} + style={{ minWidth: 280 }} + /> + + + setRuleGeneration(e.target.value)} + isLoading={genAiConnectors.loading} + style={{ minWidth: 280 }} + /> + + + setDiscovery(e.target.value)} + isLoading={genAiConnectors.loading} + style={{ minWidth: 280 }} + /> + + {saveError && ( + <> + + + + + + )} + + + + + {i18n.translate('xpack.streams.significantEventsDiscovery.settings.saveButton', { + defaultMessage: 'Save', + })} + + + + + + +
+ ); +} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/streams_view/streams_view.tsx index 28fc34900b3ce..64871b0490c9a 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/components/streams_view/streams_view.tsx @@ -86,12 +86,9 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { >({}); const router = useStreamsAppRouter(); const aiFeatures = useAIFeatures(); - const { scheduleOnboardingTask, cancelOnboardingTask } = useOnboardingApi({ - connectorId: aiFeatures?.genAiConnectors.selectedConnector, - }); - const { scheduleInsightsDiscoveryTask, getInsightsDiscoveryTaskStatus } = useInsightsDiscoveryApi( - aiFeatures?.genAiConnectors.selectedConnector - ); + const { scheduleOnboardingTask, cancelOnboardingTask } = useOnboardingApi(); + const { scheduleInsightsDiscoveryTask, getInsightsDiscoveryTaskStatus } = + useInsightsDiscoveryApi(); const [{ value: insightsTask }, getInsightsTaskStatus] = useAsyncFn( getInsightsDiscoveryTaskStatus ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/hooks/use_onboarding_status_update_queue.ts b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/hooks/use_onboarding_status_update_queue.ts index c618d2f851e0f..3745b182b138a 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/hooks/use_onboarding_status_update_queue.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/hooks/use_onboarding_status_update_queue.ts @@ -10,7 +10,6 @@ import { TaskStatus } from '@kbn/streams-schema'; import pMap from 'p-map'; import { useCallback, useRef } from 'react'; import { useOnboardingApi } from '../../../hooks/use_onboarding_api'; -import { useAIFeatures } from '../../../hooks/use_ai_features'; type StreamOnboardingStatusUpdateCallback = ( streamName: string, @@ -23,10 +22,7 @@ export function useOnboardingStatusUpdateQueue( const queue = useRef(new Set([])); const isProcessing = useRef(false); - const aiFeatures = useAIFeatures(); - const { getOnboardingTaskStatus } = useOnboardingApi({ - connectorId: aiFeatures?.genAiConnectors.selectedConnector, - }); + const { getOnboardingTaskStatus } = useOnboardingApi(); const updateStatuses = useCallback(async (): Promise => { await pMap( diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/page.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/page.tsx index 22e1fbb42ead3..e770a80d28f02 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/page.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/significant_events_discovery/page.tsx @@ -21,8 +21,9 @@ import { FeaturesTable } from './components/features_table/features_table'; import { QueriesTable } from './components/queries_table/queries_table'; import { StreamsView } from './components/streams_view/streams_view'; import { InsightsTab } from './components/insights/tab'; +import { SettingsTab } from './components/settings/tab'; -const discoveryTabs = ['streams', 'features', 'queries', 'significant_events'] as const; +const discoveryTabs = ['streams', 'features', 'queries', 'significant_events', 'settings'] as const; type DiscoveryTab = (typeof discoveryTabs)[number]; function isValidDiscoveryTab(value: string): value is DiscoveryTab { @@ -110,6 +111,14 @@ export function SignificantEventsDiscoveryPage() { href: router.link('/_discovery/{tab}', { path: { tab: 'significant_events' } }), isSelected: tab === 'significant_events', }, + { + id: 'settings', + label: i18n.translate('xpack.streams.significantEventsDiscovery.settingsTab', { + defaultMessage: 'Settings', + }), + href: router.link('/_discovery/{tab}', { path: { tab: 'settings' } }), + isSelected: tab === 'settings', + }, ]; return ( @@ -143,6 +152,7 @@ export function SignificantEventsDiscoveryPage() { {tab === 'features' && } {tab === 'queries' && } {tab === 'significant_events' && } + {tab === 'settings' && } ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/add_significant_event_flyout.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/add_significant_event_flyout.tsx index d3a43252716a9..aae04dc032bad 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/add_significant_event_flyout.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/add_significant_event_flyout.tsx @@ -78,7 +78,6 @@ export function AddSignificantEventFlyout({ const { scheduleOnboardingTask, getOnboardingTaskStatus, cancelOnboardingTask } = useOnboardingApi({ - connectorId: aiFeatures?.genAiConnectors.selectedConnector, saveQueries: false, }); @@ -193,13 +192,8 @@ export function AddSignificantEventFlyout({ }, [selectedFlow]); const generateQueries = () => { - if (!aiFeatures?.genAiConnectors.selectedConnector) { - return; - } - setSelectedFlow('ai'); setGeneratedQueries([]); - scheduleTask(); }; @@ -259,7 +253,6 @@ export function AddSignificantEventFlyout({ isGeneratingQueries={isGenerating} isSavingManualEntry={isSubmitting} selectedFlow={selectedFlow} - aiFeatures={aiFeatures} />
diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/edit_significant_event_flyout.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/edit_significant_event_flyout.tsx index 0db40b3267777..555c114ed7834 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/edit_significant_event_flyout.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/add_significant_event_flyout/edit_significant_event_flyout.tsx @@ -47,7 +47,6 @@ export const EditSignificantEventFlyout = ({ }); const { acknowledgeOnboardingTask } = useOnboardingApi({ - connectorId: aiFeatures?.genAiConnectors.selectedConnector, saveQueries: false, }); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/empty_state/index.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/empty_state/index.tsx index 8df76857157c0..22a31094d3275 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/empty_state/index.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/empty_state/index.tsx @@ -9,16 +9,13 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui import { i18n } from '@kbn/i18n'; import React from 'react'; import { SignificantEventsGenerationPanel } from '../generation_panel'; -import type { AIFeatures } from '../../../hooks/use_ai_features'; export function EmptyState({ onManualEntryClick, onGenerateSuggestionsClick, - aiFeatures, }: { onManualEntryClick: () => void; onGenerateSuggestionsClick: () => void; - aiFeatures: AIFeatures | null; }) { return ( diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/feature_identification_control.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/feature_identification_control.tsx index 98c54b17450d1..9c24d0b7259cd 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/feature_identification_control.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/feature_identification_control.tsx @@ -14,7 +14,6 @@ import { TaskStatus, type Streams } from '@kbn/streams-schema'; import type { AIFeatures } from '../../hooks/use_ai_features'; import { useStreamFeaturesApi } from '../../hooks/use_stream_features_api'; import { useTaskPolling } from '../../hooks/use_task_polling'; -import { ConnectorListButtonBase } from '../connector_list_button/connector_list_button'; interface FeatureIdentificationControlProps { definition: Streams.all.Definition; @@ -81,12 +80,9 @@ export function FeatureIdentificationControl({ }, [task?.status, refreshFeatures, onTaskStart, onTaskEnd, resetNoResultsDismissed]); const handleStartIdentification = useCallback(() => { - const connectorId = aiFeatures?.genAiConnectors.selectedConnector; - if (!connectorId) return; - onTaskStart(); - scheduleFeaturesIdentificationTask(connectorId).then(getTask); - }, [aiFeatures, onTaskStart, scheduleFeaturesIdentificationTask, getTask]); + scheduleFeaturesIdentificationTask().then(getTask); + }, [onTaskStart, scheduleFeaturesIdentificationTask, getTask]); const handleCancelIdentification = useCallback(() => { cancelTask().then(onTaskEnd); @@ -127,13 +123,13 @@ export function FeatureIdentificationControl({ case TaskStatus.InProgress: return isCancellingTask ? ( - + ) : ( ); case TaskStatus.BeingCanceled: - return ; + return ; case TaskStatus.Failed: return ( @@ -187,15 +183,14 @@ interface TriggerButtonProps { function TriggerButton({ isLoading, onClick, aiFeatures }: TriggerButtonProps) { return ( - + + {IDENTIFY_FEATURES_BUTTON_LABEL} + ); } @@ -273,21 +268,11 @@ function InProgressState({ onCancel }: InProgressStateProps) { ); } -interface CancellingStateProps { - aiFeatures: AIFeatures | null; -} - -function CancellingState({ aiFeatures }: CancellingStateProps) { +function CancellingState() { return ( - + + {CANCELLING_BUTTON_LABEL} + ); } diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/generation_panel.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/generation_panel.tsx index f368efaaf1b04..c43981622ba81 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/generation_panel.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/generation_panel.tsx @@ -18,9 +18,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AssetImage } from '../asset_image'; -import { ConnectorListButtonBase } from '../connector_list_button/connector_list_button'; import type { Flow } from './add_significant_event_flyout/types'; -import type { AIFeatures } from '../../hooks/use_ai_features'; export function SignificantEventsGenerationPanel({ onGenerateSuggestionsClick, @@ -28,14 +26,12 @@ export function SignificantEventsGenerationPanel({ isGeneratingQueries, isSavingManualEntry, selectedFlow, - aiFeatures, }: { onManualEntryClick: () => void; onGenerateSuggestionsClick: () => void; isGeneratingQueries: boolean; isSavingManualEntry: boolean; selectedFlow?: Flow; - aiFeatures: AIFeatures | null; }) { return ( @@ -75,22 +71,20 @@ export function SignificantEventsGenerationPanel({ - + + {i18n.translate( + 'xpack.streams.significantEvents.significantEventsGenerationPanel.generateSuggestionsButtonLabel', + { + defaultMessage: 'Generate suggestions', + } + )} + diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/index.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/index.tsx index d7eb1c6439f75..82f21bd996d05 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/index.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_detail_significant_events_view/index.tsx @@ -96,7 +96,6 @@ export function StreamDetailSignificantEventsView({ definition }: Props) { setInitialFlow('ai'); setIsEditFlyoutOpen(true); }} - aiFeatures={aiFeatures} /> {editFlyout(true)} diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/use_insights_discovery_api.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/use_insights_discovery_api.ts index c606d92e14609..f3a6cf6bf3140 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/use_insights_discovery_api.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/use_insights_discovery_api.ts @@ -9,7 +9,7 @@ import { useAbortController } from '@kbn/react-hooks'; import { useMemo } from 'react'; import { useKibana } from './use_kibana'; -export function useInsightsDiscoveryApi(connectorId?: string) { +export function useInsightsDiscoveryApi() { const { dependencies: { start: { @@ -28,7 +28,6 @@ export function useInsightsDiscoveryApi(connectorId?: string) { params: { body: { action: 'schedule', - connectorId, ...(streamNames && streamNames.length > 0 ? { streamNames } : {}), }, }, @@ -60,6 +59,6 @@ export function useInsightsDiscoveryApi(connectorId?: string) { }); }, }), - [connectorId, signal, streamsRepositoryClient] + [signal, streamsRepositoryClient] ); } diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/use_onboarding_api.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/use_onboarding_api.ts index 0684434d2ca1f..a961a8761caf5 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/use_onboarding_api.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/use_onboarding_api.ts @@ -11,11 +11,10 @@ import { useKibana } from './use_kibana'; import { getLast24HoursTimeRange } from '../util/time_range'; export interface UseOnboardingApiOptions { - connectorId?: string; saveQueries?: boolean; } -export function useOnboardingApi({ connectorId, saveQueries = true }: UseOnboardingApiOptions) { +export function useOnboardingApi({ saveQueries = true }: UseOnboardingApiOptions = {}) { const { dependencies: { start: { @@ -42,7 +41,6 @@ export function useOnboardingApi({ connectorId, saveQueries = true }: UseOnboard action: 'schedule' as const, from, to, - connectorId, }, }, } @@ -91,6 +89,6 @@ export function useOnboardingApi({ connectorId, saveQueries = true }: UseOnboard ); }, }), - [connectorId, saveQueries, signal, streamsRepositoryClient] + [saveQueries, signal, streamsRepositoryClient] ); } diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/use_stream_features_api.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/use_stream_features_api.ts index 2e4341eb3eb0c..e8bab9089bfd6 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/use_stream_features_api.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/use_stream_features_api.ts @@ -14,7 +14,7 @@ import { getLast24HoursTimeRange } from '../util/time_range'; interface StreamFeaturesApi { getFeaturesIdentificationStatus: () => Promise; - scheduleFeaturesIdentificationTask: (connectorId: string) => Promise; + scheduleFeaturesIdentificationTask: () => Promise; cancelFeaturesIdentificationTask: () => Promise; deleteFeature: (uuid: string) => Promise; deleteFeaturesInBulk: (uuids: string[]) => Promise; @@ -41,7 +41,7 @@ export function useStreamFeaturesApi(definition: Streams.all.Definition): Stream }, }); }, - scheduleFeaturesIdentificationTask: async (connectorId: string) => { + scheduleFeaturesIdentificationTask: async () => { const { from, to } = getLast24HoursTimeRange(); await streamsRepositoryClient.fetch('POST /internal/streams/{name}/features/_task', { signal, @@ -51,7 +51,6 @@ export function useStreamFeaturesApi(definition: Streams.all.Definition): Stream action: 'schedule', to, from, - connector_id: connectorId, }, }, });