From d41dadb078cca2613d82c9fb3b577b5493608e87 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Tue, 7 Apr 2026 17:12:47 +0200 Subject: [PATCH 01/10] feat(onboarding): add onboarding options --- .../significant_events_queries_generation.ts | 38 +++- .../features_identification/index.ts | 28 ++- .../lib/tasks/task_definitions/onboarding.ts | 87 ++++++-- .../internal/streams/onboarding/route.ts | 10 + .../onboarding_config_popover.tsx | 202 ++++++++++++++++++ .../components/streams_view/streams_view.tsx | 137 +++++++++--- .../components/streams_view/translations.ts | 49 +++++ .../use_inference_feature_connectors.ts | 51 +++++ .../public/hooks/use_onboarding_api.ts | 13 +- 9 files changed, 545 insertions(+), 70 deletions(-) create mode 100644 x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx create mode 100644 x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts diff --git a/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/significant_events_queries_generation.ts b/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/significant_events_queries_generation.ts index 410b06dd9001e..3d766e36effca 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/significant_events_queries_generation.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/significant_events_queries_generation.ts @@ -28,6 +28,7 @@ export interface SignificantEventsQueriesGenerationTaskParams { end: number; sampleDocsSize?: number; streamName: string; + connectorId?: string; } export const SIGNIFICANT_EVENTS_QUERIES_GENERATION_TASK_TYPE = @@ -49,7 +50,14 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: } const { fakeRequest } = runContext; - const { start, end, sampleDocsSize, streamName, _task } = runContext.taskInstance + const { + start, + end, + sampleDocsSize, + streamName, + connectorId: connectorIdOverride, + _task, + } = runContext.taskInstance .params as TaskParams; const { @@ -64,12 +72,14 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: }); const taskLogger = taskContext.logger.get('significant_events_queries_generation'); - const connectorId = await resolveConnectorForFeature({ - searchInferenceEndpoints: taskContext.server.searchInferenceEndpoints, - featureId: STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID, - featureName: 'query generation', - request: fakeRequest, - }); + const connectorId = + connectorIdOverride ?? + (await resolveConnectorForFeature({ + searchInferenceEndpoints: taskContext.server.searchInferenceEndpoints, + featureId: STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID, + featureName: 'query generation', + request: fakeRequest, + })); taskLogger.debug(`Using connector ${connectorId} for rule generation`); try { @@ -113,7 +123,11 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: await taskClient.complete< SignificantEventsQueriesGenerationTaskParams, SignificantEventsQueriesGenerationResult - >(_task, { start, end, sampleDocsSize, streamName }, result); + >( + _task, + { start, end, sampleDocsSize, streamName, connectorId: connectorIdOverride }, + result + ); } catch (error) { if (isDefinitionNotFoundError(error)) { taskContext.logger.debug( @@ -146,7 +160,13 @@ export function createStreamsSignificantEventsQueriesGenerationTask(taskContext: await taskClient.fail( _task, - { start, end, sampleDocsSize, streamName }, + { + start, + end, + sampleDocsSize, + streamName, + connectorId: connectorIdOverride, + }, errorMessage ); diff --git a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification/index.ts b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification/index.ts index fde8c2698cf37..8e3a4eba38208 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification/index.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/tasks/task_definitions/features_identification/index.ts @@ -335,6 +335,7 @@ export interface FeaturesIdentificationTaskParams { start: number; end: number; streamName: string; + connectorId?: string; } export const FEATURES_IDENTIFICATION_TASK_TYPE = 'streams_features_identification'; @@ -355,8 +356,13 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext } const { fakeRequest } = runContext; - const { start, end, streamName, _task } = runContext.taskInstance - .params as TaskParams; + const { + start, + end, + streamName, + connectorId: connectorIdOverride, + _task, + } = runContext.taskInstance.params as TaskParams; const runId = uuid(); const trackEmptyTelemetry = (state: 'canceled' | 'failure') => { @@ -395,12 +401,14 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext }); const taskLogger = taskContext.logger.get('features_identification', streamName); - const connectorId = await resolveConnectorForFeature({ - searchInferenceEndpoints: taskContext.server.searchInferenceEndpoints, - featureId: STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID, - featureName: 'knowledge indicator extraction', - request: fakeRequest, - }); + const connectorId = + connectorIdOverride ?? + (await resolveConnectorForFeature({ + searchInferenceEndpoints: taskContext.server.searchInferenceEndpoints, + featureId: STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID, + featureName: 'knowledge indicator extraction', + request: fakeRequest, + })); taskLogger.debug(`Using connector ${connectorId} for knowledge indicator extraction`); let hasTrackedIteration = false; @@ -509,7 +517,7 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext await taskClient.complete( _task, - { start, end, streamName }, + { start, end, streamName, connectorId: connectorIdOverride }, { features: allFeatures, durationMs, @@ -566,7 +574,7 @@ export function createStreamsFeaturesIdentificationTask(taskContext: TaskContext await taskClient.fail( _task, - { start, end, streamName }, + { start, end, streamName, connectorId: connectorIdOverride }, errorMessage, { features: [], 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 b8ad384090de0..659f5a5d2971b 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 @@ -42,6 +42,10 @@ export interface OnboardingTaskParams { to: number; steps: OnboardingStep[]; saveQueries: boolean; + connectors?: { + features?: string; + queries?: string; + }; } export const STREAMS_ONBOARDING_TASK_TYPE = 'streams_onboarding'; @@ -51,6 +55,31 @@ export function getOnboardingTaskId(streamName: string, saveQueries: boolean = t return saveQueries ? base : `${base}_no_save_queries`; } +const FEATURES_IDENTIFICATION_RECENCY_MS = 12 * 60 * 60 * 1000; // 12 hours + +async function areFeaturesUpToDate({ + taskClient, + featuresTaskId, +}: { + taskClient: TaskClient; + featuresTaskId: string; +}) { + const featuresTask = await taskClient.get< + FeaturesIdentificationTaskParams, + IdentifyFeaturesResult + >(featuresTaskId); + + if (featuresTask.status !== TaskStatus.Completed) { + return false; + } + + return Boolean( + featuresTask.last_completed_at && + Date.now() - new Date(featuresTask.last_completed_at).getTime() < + FEATURES_IDENTIFICATION_RECENCY_MS + ); +} + export function createStreamsOnboardingTask(taskContext: TaskContext) { return { [STREAMS_ONBOARDING_TASK_TYPE]: { @@ -64,8 +93,8 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { } const { fakeRequest } = runContext; - const { streamName, from, to, steps, saveQueries, _task } = runContext.taskInstance - .params as TaskParams; + const { streamName, from, to, steps, saveQueries, connectors, _task } = runContext + .taskInstance.params as TaskParams; const { taskClient, queryClient, streamsClient } = await taskContext.getScopedClients( { @@ -83,21 +112,34 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { switch (step) { case OnboardingStep.FeaturesIdentification: { const featuresTaskId = getFeaturesIdentificationTaskId(streamName); - - await scheduleFeaturesIdentificationTask( - { - start: from, - end: to, - streamName, - }, - taskClient, - fakeRequest - ); - - featuresTaskResult = await waitForSubtask< - FeaturesIdentificationTaskParams, - IdentifyFeaturesResult - >(featuresTaskId, runContext.taskInstance.id, taskClient); + const isFeaturesOnlyStep = + steps.length === 1 && steps[0] === OnboardingStep.FeaturesIdentification; + + if ( + !isFeaturesOnlyStep && + (await areFeaturesUpToDate({ taskClient, featuresTaskId })) + ) { + featuresTaskResult = await taskClient.getStatus< + FeaturesIdentificationTaskParams, + IdentifyFeaturesResult + >(featuresTaskId); + } else { + await scheduleFeaturesIdentificationTask( + { + start: from, + end: to, + streamName, + connectorId: connectors?.features, + }, + taskClient, + fakeRequest + ); + + featuresTaskResult = await waitForSubtask< + FeaturesIdentificationTaskParams, + IdentifyFeaturesResult + >(featuresTaskId, runContext.taskInstance.id, taskClient); + } if (featuresTaskResult.status !== TaskStatus.Completed) { return; @@ -111,6 +153,7 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { start: from, end: to, streamName, + connectorId: connectors?.queries, }, taskClient, fakeRequest @@ -140,7 +183,14 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { await taskClient.complete( _task, - { streamName, from, to, steps, saveQueries }, + { + streamName, + from, + to, + steps, + saveQueries, + connectors, + }, { featuresTaskResult, queriesTaskResult } ); } catch (error) { @@ -168,6 +218,7 @@ export function createStreamsOnboardingTask(taskContext: TaskContext) { to, steps, saveQueries, + connectors, }, errorMessage ); 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 f3decfed4279f..8e54c1a81f675 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 @@ -53,6 +53,15 @@ export const onboardingTaskRoute = createServerRoute({ .describe( 'Optional list of steps to perform as part of stream onboarding in the specified sequence. By default it will execute all steps.' ), + connectors: z + .object({ + features: z.string().optional().describe('Connector ID for features identification.'), + queries: z.string().optional().describe('Connector ID for queries generation.'), + }) + .optional() + .describe( + 'Optional per-step connector overrides. When omitted the server resolves connectors from the inference feature registry.' + ), }), }), handler: async ({ params, request, getScopedClients, server }): Promise => { @@ -85,6 +94,7 @@ export const onboardingTaskRoute = createServerRoute({ to: body.to, steps: body.steps, saveQueries, + connectors: body.connectors, }, request, }, diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx new file mode 100644 index 0000000000000..63904fce5dd00 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx @@ -0,0 +1,202 @@ +/* + * 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 { + EuiButton, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPopover, + EuiPopoverTitle, + EuiSuperSelect, + EuiSwitch, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { useBoolean } from '@kbn/react-hooks'; +import { OnboardingStep } from '@kbn/streams-schema'; +import React, { useCallback } from 'react'; +import type { UseInferenceFeatureConnectorsResult } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; +import { ConnectorIcon } from '../../../../connector_list_button/connector_icon'; +import { + FEATURES_STEP_LABEL, + ONBOARDING_CONFIG_POPOVER_ARIA_LABEL, + ONBOARDING_CONFIG_POPOVER_TITLE, + QUERIES_STEP_LABEL, + RUN_BUTTON_LABEL, +} from './translations'; + +export interface OnboardingConfig { + steps: OnboardingStep[]; + connectors: { + features?: string; + queries?: string; + }; +} + +interface StepRowProps { + step: OnboardingStep; + label: string; + enabled: boolean; + connectorId: string | undefined; + connectors: UseInferenceFeatureConnectorsResult; + onToggle: (step: OnboardingStep, enabled: boolean) => void; + onConnectorChange: (step: OnboardingStep, connectorId: string) => void; +} + +const StepRow = ({ + step, + label, + enabled, + connectorId, + connectors, + onToggle, + onConnectorChange, +}: StepRowProps) => { + const selectId = useGeneratedHtmlId({ prefix: `onboardingStep_${step}` }); + + const connectorOptions = connectors.allConnectors.map((connector) => ({ + value: connector.connectorId, + inputDisplay: ( + + + + + {connector.name} + + ), + })); + + return ( + + + onToggle(step, e.target.checked)} + compressed + /> + + {connectorOptions.length >= 1 && ( + + + onConnectorChange(step, value)} + disabled={!enabled} + compressed + fullWidth + /> + + + )} + + ); +}; + +const STEP_ORDER = [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration]; + +interface OnboardingConfigPopoverProps { + config: OnboardingConfig; + featuresConnectors: UseInferenceFeatureConnectorsResult; + queriesConnectors: UseInferenceFeatureConnectorsResult; + onConfigChange: (config: OnboardingConfig) => void; + onRun: () => void; + isRunDisabled: boolean; +} + +export const OnboardingConfigPopover = ({ + config, + featuresConnectors, + queriesConnectors, + onConfigChange, + onRun, + isRunDisabled, +}: OnboardingConfigPopoverProps) => { + const [isOpen, { off: close, toggle }] = useBoolean(false); + const popoverId = useGeneratedHtmlId({ prefix: 'onboardingConfigPopover' }); + + const handleToggle = useCallback( + (step: OnboardingStep, enabled: boolean) => { + const toggled = enabled ? [...config.steps, step] : config.steps.filter((s) => s !== step); + onConfigChange({ ...config, steps: STEP_ORDER.filter((s) => toggled.includes(s)) }); + }, + [config, onConfigChange] + ); + + const handleConnectorChange = useCallback( + (step: OnboardingStep, newConnectorId: string) => { + const key = step === OnboardingStep.FeaturesIdentification ? 'features' : 'queries'; + onConfigChange({ ...config, connectors: { ...config.connectors, [key]: newConnectorId } }); + }, + [config, onConfigChange] + ); + + const handleRun = useCallback(() => { + close(); + onRun(); + }, [close, onRun]); + + return ( + + } + panelPaddingSize="m" + > + {ONBOARDING_CONFIG_POPOVER_TITLE} + + + + + + + + + + {RUN_BUTTON_LABEL} + + + + + ); +}; diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index dec1eb00ffa32..2958004d8d2b2 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -18,23 +18,34 @@ import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; import type { OnboardingResult, TaskResult } from '@kbn/streams-schema'; -import { TaskStatus } from '@kbn/streams-schema'; +import { + OnboardingStep, + STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID, + STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID, + TaskStatus, +} from '@kbn/streams-schema'; import pMap from 'p-map'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; import type { TableRow } from './utils'; import { useAIFeatures } from '../../../../../hooks/use_ai_features'; import { useIndexPatternsConfig } from '../../../../../hooks/use_index_patterns_config'; import { useKibana } from '../../../../../hooks/use_kibana'; import { useInsightsDiscoveryApi } from '../../../../../hooks/sig_events/use_insights_discovery_api'; +import { useInferenceFeatureConnectors } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; +import type { ScheduleOnboardingOptions } from '../../../../../hooks/use_onboarding_api'; import { useOnboardingApi } from '../../../../../hooks/use_onboarding_api'; import { useStreamsAppRouter } from '../../../../../hooks/use_streams_app_router'; import { useTaskPolling } from '../../../../../hooks/use_task_polling'; import { getFormattedError } from '../../../../../util/errors'; import { StreamsAppSearchBar } from '../../../../streams_app_search_bar'; import { useOnboardingStatusUpdateQueue } from '../../hooks/use_onboarding_status_update_queue'; +import type { OnboardingConfig } from './onboarding_config_popover'; +import { OnboardingConfigPopover } from './onboarding_config_popover'; import { DISCOVER_INSIGHTS_BUTTON_LABEL, + GENERATE_FEATURES_BUTTON_LABEL, + GENERATE_QUERIES_BUTTON_LABEL, getInsightsCompleteToastTitle, INSIGHTS_COMPLETE_TOAST_VIEW_BUTTON, INSIGHTS_SCHEDULING_FAILURE_TITLE, @@ -71,6 +82,33 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const [isWaitingForInsightsTask, setIsWaitingForInsightsTask] = useState(false); const { filterStreamsByIndexPatterns } = useIndexPatternsConfig(); + const featuresConnectors = useInferenceFeatureConnectors( + STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID + ); + const queriesConnectors = useInferenceFeatureConnectors( + STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID + ); + + const [onboardingConfig, setOnboardingConfig] = useState({ + steps: [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration], + connectors: {}, + }); + + useEffect(() => { + setOnboardingConfig((prev) => ({ + ...prev, + connectors: { + ...prev.connectors, + ...(featuresConnectors.resolvedConnector && !prev.connectors.features + ? { features: featuresConnectors.resolvedConnector.connectorId } + : {}), + ...(queriesConnectors.resolvedConnector && !prev.connectors.queries + ? { queries: queriesConnectors.resolvedConnector.connectorId } + : {}), + }, + })); + }, [featuresConnectors.resolvedConnector, queriesConnectors.resolvedConnector]); + const streamsListFetch = useFetchStreams({ select: (result) => { return { @@ -211,51 +249,59 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { }); }, [onboardingStatusUpdateQueue, processStatusUpdateQueue, streamsListFetch.data]); - const bulkScheduleOnboardingTask = async (streamList: string[]) => { + const getActionableStreamNames = () => + selectedStreams + .filter((item) => { + const status = streamOnboardingResultMap[item.stream.name]?.status; + return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status); + }) + .map((item) => item.stream.name); + + const bulkScheduleOnboardingTask = async ( + streamList: string[], + options?: ScheduleOnboardingOptions + ) => { try { await pMap( streamList, async (streamName) => { - await scheduleOnboardingTask(streamName); + await scheduleOnboardingTask(streamName, options); }, { concurrency: 10 } ); } catch (error) { - toasts.addError(getFormattedError(error), { - title: ONBOARDING_SCHEDULING_FAILURE_TITLE, - }); + toasts.addError(getFormattedError(error), { title: ONBOARDING_SCHEDULING_FAILURE_TITLE }); } - }; - - const onBulkOnboardStreamsClick = async () => { - const streamList = selectedStreams - .filter((item) => { - const onboardingResult = streamOnboardingResultMap[item.stream.name]; - - return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(onboardingResult.status); - }) - .map((item) => item.stream.name); - - setSelectedStreams([]); - await bulkScheduleOnboardingTask(streamList); streamList.forEach((streamName) => { onboardingStatusUpdateQueue.add(streamName); }); processStatusUpdateQueue(); }; + const onBulkOnboardStreamsClick = async () => { + const streamList = getActionableStreamNames(); + setSelectedStreams([]); + await bulkScheduleOnboardingTask(streamList, onboardingConfig); + }; + const onOnboardStreamActionClick = async (streamName: string) => { await bulkScheduleOnboardingTask([streamName]); - - onboardingStatusUpdateQueue.add(streamName); - processStatusUpdateQueue(); }; const onStopOnboardingActionClick = (streamName: string) => { cancelOnboardingTask(streamName); }; + const dynamicButtonLabel = useMemo(() => { + const { steps } = onboardingConfig; + const hasFeatures = steps.includes(OnboardingStep.FeaturesIdentification); + const hasQueries = steps.includes(OnboardingStep.QueriesGeneration); + if (hasFeatures && !hasQueries) return GENERATE_FEATURES_BUTTON_LABEL; + if (hasQueries && !hasFeatures) return GENERATE_QUERIES_BUTTON_LABEL; + return RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL; + }, [onboardingConfig]); + return ( @@ -288,14 +334,31 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { )} - - {RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL} - + + + + + {dynamicButtonLabel} + + + + + + + { + const status = streamOnboardingResultMap[row.stream.name]?.status; + return ( + status === undefined || + ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status) + ); + }, + }} onOnboardStreamActionClick={onOnboardStreamActionClick} onStopOnboardingActionClick={onStopOnboardingActionClick} /> diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts index 4e7cc23c3d1d2..9219bbfa48c93 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts @@ -145,3 +145,52 @@ export const NO_INSIGHTS_TOAST_TITLE = i18n.translate( defaultMessage: 'No Significant Events found', } ); + +export const GENERATE_FEATURES_BUTTON_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.generateFeaturesButtonLabel', + { + defaultMessage: 'Generate Features', + } +); + +export const GENERATE_QUERIES_BUTTON_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.generateQueriesButtonLabel', + { + defaultMessage: 'Generate Queries', + } +); + +export const ONBOARDING_CONFIG_POPOVER_ARIA_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.onboardingConfigPopoverAriaLabel', + { + defaultMessage: 'Configure onboarding steps and connectors', + } +); + +export const ONBOARDING_CONFIG_POPOVER_TITLE = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.onboardingConfigPopoverTitle', + { + defaultMessage: 'Onboarding configuration', + } +); + +export const FEATURES_STEP_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.featuresStepLabel', + { + defaultMessage: 'Feature identification', + } +); + +export const QUERIES_STEP_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.queriesStepLabel', + { + defaultMessage: 'Query generation', + } +); + +export const RUN_BUTTON_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.runButtonLabel', + { + defaultMessage: 'Run', + } +); diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts new file mode 100644 index 0000000000000..e168567b7efd5 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts @@ -0,0 +1,51 @@ +/* + * 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 type { InferenceConnector } from '@kbn/inference-common'; +import { + INFERENCE_CONNECTORS_INTERNAL_API_PATH, + type InferenceConnectorsApiResponseBody, +} from '@kbn/inference-common'; +import { useAbortController } from '@kbn/react-hooks'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { useKibana } from '../use_kibana'; + +export interface UseInferenceFeatureConnectorsResult { + resolvedConnector: InferenceConnector | undefined; + allConnectors: InferenceConnector[]; + loading: boolean; + error: Error | undefined; +} + +export function useInferenceFeatureConnectors( + featureId: string +): UseInferenceFeatureConnectorsResult { + const { core } = useKibana(); + const { signal } = useAbortController(); + + const [state, fetchConnectors] = useAsyncFn( + () => + core.http.get(INFERENCE_CONNECTORS_INTERNAL_API_PATH, { + query: { featureId }, + version: '1', + signal, + }), + [core.http, featureId, signal] + ); + + useEffect(() => { + fetchConnectors(); + }, [fetchConnectors]); + + return { + resolvedConnector: state.value?.connectors[0], + allConnectors: state.value?.allConnectors ?? [], + loading: state.loading, + error: state.error, + }; +} 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 a961a8761caf5..15b1d6ac0ecfd 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 @@ -6,6 +6,7 @@ */ import { useAbortController } from '@kbn/react-hooks'; +import type { OnboardingStep } from '@kbn/streams-schema'; import { useMemo } from 'react'; import { useKibana } from './use_kibana'; import { getLast24HoursTimeRange } from '../util/time_range'; @@ -14,6 +15,14 @@ export interface UseOnboardingApiOptions { saveQueries?: boolean; } +export interface ScheduleOnboardingOptions { + steps?: OnboardingStep[]; + connectors?: { + features?: string; + queries?: string; + }; +} + export function useOnboardingApi({ saveQueries = true }: UseOnboardingApiOptions = {}) { const { dependencies: { @@ -27,7 +36,7 @@ export function useOnboardingApi({ saveQueries = true }: UseOnboardingApiOptions return useMemo( () => ({ - scheduleOnboardingTask: async (streamName: string) => { + scheduleOnboardingTask: async (streamName: string, options?: ScheduleOnboardingOptions) => { const { from, to } = getLast24HoursTimeRange(); return streamsRepositoryClient.fetch( @@ -41,6 +50,8 @@ export function useOnboardingApi({ saveQueries = true }: UseOnboardingApiOptions action: 'schedule' as const, from, to, + ...(options?.steps !== undefined && { steps: options.steps }), + ...(options?.connectors !== undefined && { connectors: options.connectors }), }, }, } From 89c4ab072c7a00813ba535c0934bde551e02ed61 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 10:37:33 +0200 Subject: [PATCH 02/10] fix(cr): code review --- .../onboarding_config_popover.tsx | 74 +++++++++++-------- .../components/streams_view/streams_view.tsx | 54 +++++++------- .../components/streams_view/translations.ts | 12 +-- .../use_inference_feature_connectors.ts | 10 +-- 4 files changed, 77 insertions(+), 73 deletions(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx index 63904fce5dd00..b3d6e39ce64b2 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx @@ -17,6 +17,7 @@ import { EuiSwitch, useGeneratedHtmlId, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useBoolean } from '@kbn/react-hooks'; import { OnboardingStep } from '@kbn/streams-schema'; import React, { useCallback } from 'react'; @@ -38,11 +39,21 @@ export interface OnboardingConfig { }; } +const STEP_CONNECTOR_KEY: Record = { + [OnboardingStep.FeaturesIdentification]: 'features', + [OnboardingStep.QueriesGeneration]: 'queries', +}; + +const STEPS: ReadonlyArray<{ step: OnboardingStep; label: string }> = [ + { step: OnboardingStep.FeaturesIdentification, label: FEATURES_STEP_LABEL }, + { step: OnboardingStep.QueriesGeneration, label: QUERIES_STEP_LABEL }, +]; + interface StepRowProps { step: OnboardingStep; label: string; enabled: boolean; - connectorId: string | undefined; + displayConnectorId: string | undefined; connectors: UseInferenceFeatureConnectorsResult; onToggle: (step: OnboardingStep, enabled: boolean) => void; onConnectorChange: (step: OnboardingStep, connectorId: string) => void; @@ -52,7 +63,7 @@ const StepRow = ({ step, label, enabled, - connectorId, + displayConnectorId, connectors, onToggle, onConnectorChange, @@ -81,13 +92,13 @@ const StepRow = ({ compressed /> - {connectorOptions.length >= 1 && ( + {displayConnectorId && connectorOptions.length > 0 && ( onConnectorChange(step, value)} disabled={!enabled} compressed @@ -100,10 +111,9 @@ const StepRow = ({ ); }; -const STEP_ORDER = [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration]; - interface OnboardingConfigPopoverProps { config: OnboardingConfig; + displayConnectors: OnboardingConfig['connectors']; featuresConnectors: UseInferenceFeatureConnectorsResult; queriesConnectors: UseInferenceFeatureConnectorsResult; onConfigChange: (config: OnboardingConfig) => void; @@ -111,8 +121,13 @@ interface OnboardingConfigPopoverProps { isRunDisabled: boolean; } +const popoverContentStyle = css` + min-width: 280px; +`; + export const OnboardingConfigPopover = ({ config, + displayConnectors, featuresConnectors, queriesConnectors, onConfigChange, @@ -122,17 +137,23 @@ export const OnboardingConfigPopover = ({ const [isOpen, { off: close, toggle }] = useBoolean(false); const popoverId = useGeneratedHtmlId({ prefix: 'onboardingConfigPopover' }); + const connectorsByStep: Record = { + [OnboardingStep.FeaturesIdentification]: featuresConnectors, + [OnboardingStep.QueriesGeneration]: queriesConnectors, + }; + const handleToggle = useCallback( (step: OnboardingStep, enabled: boolean) => { const toggled = enabled ? [...config.steps, step] : config.steps.filter((s) => s !== step); - onConfigChange({ ...config, steps: STEP_ORDER.filter((s) => toggled.includes(s)) }); + const ordered = STEPS.map((s) => s.step).filter((s) => toggled.includes(s)); + onConfigChange({ ...config, steps: ordered }); }, [config, onConfigChange] ); const handleConnectorChange = useCallback( (step: OnboardingStep, newConnectorId: string) => { - const key = step === OnboardingStep.FeaturesIdentification ? 'features' : 'queries'; + const key = STEP_CONNECTOR_KEY[step]; onConfigChange({ ...config, connectors: { ...config.connectors, [key]: newConnectorId } }); }, [config, onConfigChange] @@ -162,29 +183,20 @@ export const OnboardingConfigPopover = ({ panelPaddingSize="m" > {ONBOARDING_CONFIG_POPOVER_TITLE} - - - - - - - + + {STEPS.map(({ step, label }) => ( + + + + ))} { - setOnboardingConfig((prev) => ({ - ...prev, - connectors: { - ...prev.connectors, - ...(featuresConnectors.resolvedConnector && !prev.connectors.features - ? { features: featuresConnectors.resolvedConnector.connectorId } - : {}), - ...(queriesConnectors.resolvedConnector && !prev.connectors.queries - ? { queries: queriesConnectors.resolvedConnector.connectorId } - : {}), - }, - })); - }, [featuresConnectors.resolvedConnector, queriesConnectors.resolvedConnector]); + const displayConnectors = useMemo( + () => ({ + features: + onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnector?.connectorId, + queries: + onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnector?.connectorId, + }), + [ + onboardingConfig.connectors, + featuresConnectors.resolvedConnector, + queriesConnectors.resolvedConnector, + ] + ); const streamsListFetch = useFetchStreams({ select: (result) => { @@ -249,12 +248,14 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { }); }, [onboardingStatusUpdateQueue, processStatusUpdateQueue, streamsListFetch.data]); + const isStreamActionable = (streamName: string) => { + const status = streamOnboardingResultMap[streamName]?.status; + return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status); + }; + const getActionableStreamNames = () => selectedStreams - .filter((item) => { - const status = streamOnboardingResultMap[item.stream.name]?.status; - return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status); - }) + .filter((item) => isStreamActionable(item.stream.name)) .map((item) => item.stream.name); const bulkScheduleOnboardingTask = async ( @@ -293,14 +294,14 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { cancelOnboardingTask(streamName); }; + const { steps: selectedSteps } = onboardingConfig; const dynamicButtonLabel = useMemo(() => { - const { steps } = onboardingConfig; - const hasFeatures = steps.includes(OnboardingStep.FeaturesIdentification); - const hasQueries = steps.includes(OnboardingStep.QueriesGeneration); + const hasFeatures = selectedSteps.includes(OnboardingStep.FeaturesIdentification); + const hasQueries = selectedSteps.includes(OnboardingStep.QueriesGeneration); if (hasFeatures && !hasQueries) return GENERATE_FEATURES_BUTTON_LABEL; if (hasQueries && !hasFeatures) return GENERATE_QUERIES_BUTTON_LABEL; return RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL; - }, [onboardingConfig]); + }, [selectedSteps]); return ( @@ -350,6 +351,7 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { { - const status = streamOnboardingResultMap[row.stream.name]?.status; - return ( - status === undefined || - ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status) - ); - }, + selectable: (row) => isStreamActionable(row.stream.name), }} onOnboardStreamActionClick={onOnboardStreamActionClick} onStopOnboardingActionClick={onStopOnboardingActionClick} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts index 9219bbfa48c93..33ef61e1bafcc 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts @@ -21,14 +21,14 @@ export const SIGNIFICANT_EVENTS_COLUMN_HEADER = i18n.translate( export const QUERIES_COLUMN_HEADER = i18n.translate( 'xpack.streams.significantEventsDiscovery.streamsTree.queriesColumnName', { - defaultMessage: 'Queries', + defaultMessage: 'KI Queries', } ); export const KNOWLEDGE_INDICATORS_COLUMN_HEADER = i18n.translate( 'xpack.streams.significantEventsDiscovery.streamsTree.knowledgeIndicatorsColumnName', { - defaultMessage: 'Knowledge Indicators', + defaultMessage: 'KI Features', } ); @@ -149,14 +149,14 @@ export const NO_INSIGHTS_TOAST_TITLE = i18n.translate( export const GENERATE_FEATURES_BUTTON_LABEL = i18n.translate( 'xpack.streams.significantEventsDiscovery.streamsView.generateFeaturesButtonLabel', { - defaultMessage: 'Generate Features', + defaultMessage: 'Generate KI Features', } ); export const GENERATE_QUERIES_BUTTON_LABEL = i18n.translate( 'xpack.streams.significantEventsDiscovery.streamsView.generateQueriesButtonLabel', { - defaultMessage: 'Generate Queries', + defaultMessage: 'Generate KI Queries', } ); @@ -177,14 +177,14 @@ export const ONBOARDING_CONFIG_POPOVER_TITLE = i18n.translate( export const FEATURES_STEP_LABEL = i18n.translate( 'xpack.streams.significantEventsDiscovery.streamsView.featuresStepLabel', { - defaultMessage: 'Feature identification', + defaultMessage: 'KI Features', } ); export const QUERIES_STEP_LABEL = i18n.translate( 'xpack.streams.significantEventsDiscovery.streamsView.queriesStepLabel', { - defaultMessage: 'Query generation', + defaultMessage: 'KI Queries', } ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts index e168567b7efd5..2049c5318801b 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { useEffect } from 'react'; import type { InferenceConnector } from '@kbn/inference-common'; import { INFERENCE_CONNECTORS_INTERNAL_API_PATH, type InferenceConnectorsApiResponseBody, } from '@kbn/inference-common'; import { useAbortController } from '@kbn/react-hooks'; -import useAsyncFn from 'react-use/lib/useAsyncFn'; +import useAsync from 'react-use/lib/useAsync'; import { useKibana } from '../use_kibana'; export interface UseInferenceFeatureConnectorsResult { @@ -28,7 +27,7 @@ export function useInferenceFeatureConnectors( const { core } = useKibana(); const { signal } = useAbortController(); - const [state, fetchConnectors] = useAsyncFn( + const state = useAsync( () => core.http.get(INFERENCE_CONNECTORS_INTERNAL_API_PATH, { query: { featureId }, @@ -38,11 +37,8 @@ export function useInferenceFeatureConnectors( [core.http, featureId, signal] ); - useEffect(() => { - fetchConnectors(); - }, [fetchConnectors]); - return { + // The API returns the feature-matched connector as the first element of `connectors` resolvedConnector: state.value?.connectors[0], allConnectors: state.value?.allConnectors ?? [], loading: state.loading, From e0081458af067a73744b737c758df231b983e058 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 10:49:36 +0200 Subject: [PATCH 03/10] fix(style): min width for onboarding button --- .../components/streams_view/streams_view.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index c3d38deb25f1b..6bf0eaa17dff3 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -58,6 +58,10 @@ import { import { StreamsTreeTable } from './tree_table'; import { useFetchStreams } from '../../hooks/use_fetch_streams'; +const onboardingButtonStyle = css` + min-width: 160px; +`; + const datePickerStyle = css` .euiFormControlLayout, .euiSuperDatePicker button, @@ -343,6 +347,7 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { iconType="radar" disabled={selectedStreams.length === 0 || onboardingConfig.steps.length === 0} size="xs" + css={onboardingButtonStyle} data-test-subj="significant_events_onboard_streams_button" > {dynamicButtonLabel} From cfd671cc87288469f7bde4b10c3507968553f027 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 14:25:06 +0200 Subject: [PATCH 04/10] feat(insights): add connector selector for insights --- .../sig_events/tasks/insights_discovery.ts | 26 +++-- .../internal/sig_events/insights/route.ts | 7 ++ .../streams_view/connector_select_options.tsx | 24 ++++ .../insights_connector_popover.tsx | 108 ++++++++++++++++++ .../onboarding_config_popover.tsx | 14 +-- .../components/streams_view/streams_view.tsx | 60 ++++++++-- .../components/streams_view/translations.ts | 14 +++ .../sig_events/use_insights_discovery_api.ts | 3 +- 8 files changed, 221 insertions(+), 35 deletions(-) create mode 100644 x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx create mode 100644 x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx diff --git a/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/insights_discovery.ts b/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/insights_discovery.ts index 7b5f8b03b3dd6..410f0edc6f0a1 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/insights_discovery.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/sig_events/tasks/insights_discovery.ts @@ -31,6 +31,7 @@ export interface InsightsDiscoveryTaskResult { export interface InsightsDiscoveryTaskParams { /** When provided, only generate insights for these stream names. Otherwise all streams are used. */ streamNames?: string[]; + connectorId?: string; } export const STREAMS_INSIGHTS_DISCOVERY_TASK_TYPE = 'streams_insights_discovery'; @@ -47,8 +48,11 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { } const { fakeRequest } = runContext; - const { streamNames, _task } = runContext.taskInstance - .params as TaskParams; + const { + streamNames, + connectorId: connectorIdOverride, + _task, + } = runContext.taskInstance.params as TaskParams; const { taskClient, @@ -62,12 +66,14 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { }); const taskLogger = taskContext.logger.get('insights_discovery'); - const connectorId = await resolveConnectorForFeature({ - searchInferenceEndpoints: taskContext.server.searchInferenceEndpoints, - featureId: STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID, - featureName: 'discovery', - request: fakeRequest, - }); + const connectorId = + connectorIdOverride ?? + (await resolveConnectorForFeature({ + searchInferenceEndpoints: taskContext.server.searchInferenceEndpoints, + featureId: STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID, + featureName: 'discovery', + request: fakeRequest, + })); taskLogger.debug(`Using connector ${connectorId} for discovery`); const boundInferenceClient = inferenceClient.bindTo({ connectorId }); @@ -116,7 +122,7 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { await taskClient.complete( _task, - { streamNames }, + { streamNames, connectorId: connectorIdOverride }, { insights, tokensUsed: result.tokens_used } ); } catch (error) { @@ -144,7 +150,7 @@ export function createStreamsInsightsDiscoveryTask(taskContext: TaskContext) { await taskClient.fail( _task, - { streamNames }, + { streamNames, connectorId: connectorIdOverride }, errorMessage ); return getDeleteTaskRunResult(); diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/sig_events/insights/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/sig_events/insights/route.ts index ecd96a25d6cab..169ee725d2363 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/internal/sig_events/insights/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/sig_events/insights/route.ts @@ -47,6 +47,12 @@ const insightsTaskRoute = createServerRoute({ .array(z.string()) .describe('List of stream names to generate insights for.') .optional(), + connectorId: z + .string() + .optional() + .describe( + 'Optional connector ID override. When omitted the server resolves the connector from the inference feature registry.' + ), }), }), handler: async ({ params, request, getScopedClients, server }): Promise => { @@ -67,6 +73,7 @@ const insightsTaskRoute = createServerRoute({ taskId: STREAMS_INSIGHTS_DISCOVERY_TASK_TYPE, params: { streamNames: body.streamNames, + connectorId: body.connectorId, }, request, }, diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx new file mode 100644 index 0000000000000..011dc65ac3588 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx @@ -0,0 +1,24 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { InferenceConnector } from '@kbn/inference-common'; +import React from 'react'; +import { ConnectorIcon } from '../../../../connector_list_button/connector_icon'; + +export const buildConnectorSelectOptions = (connectors: InferenceConnector[]) => + connectors.map((connector) => ({ + value: connector.connectorId, + inputDisplay: ( + + + + + {connector.name} + + ), + })); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx new file mode 100644 index 0000000000000..24b7784660693 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx @@ -0,0 +1,108 @@ +/* + * 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 { + EuiButton, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPopover, + EuiPopoverTitle, + EuiSuperSelect, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useBoolean } from '@kbn/react-hooks'; +import React, { useCallback } from 'react'; +import type { UseInferenceFeatureConnectorsResult } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; +import { buildConnectorSelectOptions } from './connector_select_options'; +import { + INSIGHTS_CONNECTOR_POPOVER_ARIA_LABEL, + INSIGHTS_CONNECTOR_POPOVER_TITLE, + RUN_BUTTON_LABEL, +} from './translations'; + +interface InsightsConnectorPopoverProps { + displayConnectorId: string | undefined; + connectors: UseInferenceFeatureConnectorsResult; + onConnectorChange: (connectorId: string) => void; + onRun: () => void; + isRunDisabled: boolean; +} + +const popoverContentStyle = css` + min-width: 280px; +`; + +export const InsightsConnectorPopover = ({ + displayConnectorId, + connectors, + onConnectorChange, + onRun, + isRunDisabled, +}: InsightsConnectorPopoverProps) => { + const [isOpen, { off: close, toggle }] = useBoolean(false); + const popoverId = useGeneratedHtmlId({ prefix: 'insightsConnectorPopover' }); + const selectId = useGeneratedHtmlId({ prefix: 'insightsConnectorSelect' }); + + const connectorOptions = buildConnectorSelectOptions(connectors.allConnectors); + + const handleRun = useCallback(() => { + close(); + onRun(); + }, [close, onRun]); + + return ( + + } + panelPaddingSize="m" + > + {INSIGHTS_CONNECTOR_POPOVER_TITLE} + + {displayConnectorId && connectorOptions.length > 0 && ( + + + + + + )} + + + {RUN_BUTTON_LABEL} + + + + + ); +}; diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx index b3d6e39ce64b2..dfd7e87cdcec3 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx @@ -22,7 +22,7 @@ import { useBoolean } from '@kbn/react-hooks'; import { OnboardingStep } from '@kbn/streams-schema'; import React, { useCallback } from 'react'; import type { UseInferenceFeatureConnectorsResult } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; -import { ConnectorIcon } from '../../../../connector_list_button/connector_icon'; +import { buildConnectorSelectOptions } from './connector_select_options'; import { FEATURES_STEP_LABEL, ONBOARDING_CONFIG_POPOVER_ARIA_LABEL, @@ -70,17 +70,7 @@ const StepRow = ({ }: StepRowProps) => { const selectId = useGeneratedHtmlId({ prefix: `onboardingStep_${step}` }); - const connectorOptions = connectors.allConnectors.map((connector) => ({ - value: connector.connectorId, - inputDisplay: ( - - - - - {connector.name} - - ), - })); + const connectorOptions = buildConnectorSelectOptions(connectors.allConnectors); return ( diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index 6bf0eaa17dff3..6b7f7c35f1e01 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -20,6 +20,7 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import type { OnboardingResult, TaskResult } from '@kbn/streams-schema'; import { OnboardingStep, + STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID, STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID, STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID, TaskStatus, @@ -40,6 +41,7 @@ import { useTaskPolling } from '../../../../../hooks/use_task_polling'; import { getFormattedError } from '../../../../../util/errors'; import { StreamsAppSearchBar } from '../../../../streams_app_search_bar'; import { useOnboardingStatusUpdateQueue } from '../../hooks/use_onboarding_status_update_queue'; +import { InsightsConnectorPopover } from './insights_connector_popover'; import type { OnboardingConfig } from './onboarding_config_popover'; import { OnboardingConfigPopover } from './onboarding_config_popover'; import { @@ -92,6 +94,15 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const queriesConnectors = useInferenceFeatureConnectors( STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID ); + const discoveryConnectors = useInferenceFeatureConnectors( + STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID + ); + + const [discoveryConnectorOverride, setDiscoveryConnectorOverride] = useState< + string | undefined + >(); + const displayDiscoveryConnectorId = + discoveryConnectorOverride ?? discoveryConnectors.resolvedConnector?.connectorId; const [onboardingConfig, setOnboardingConfig] = useState({ steps: [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration], @@ -146,7 +157,7 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const streamNames = selectedStreams.length > 0 ? selectedStreams.map((row) => row.stream.name) : undefined; try { - await scheduleInsightsDiscoveryTask(streamNames); + await scheduleInsightsDiscoveryTask(streamNames, discoveryConnectorOverride); setIsWaitingForInsightsTask(true); await getInsightsTaskStatus(); } catch (error) { @@ -155,7 +166,13 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { }); throw error; } - }, [scheduleInsightsDiscoveryTask, selectedStreams, toasts, getInsightsTaskStatus]); + }, [ + scheduleInsightsDiscoveryTask, + selectedStreams, + discoveryConnectorOverride, + toasts, + getInsightsTaskStatus, + ]); // When we started the insights task from this view and it completes, show toast useEffect(() => { @@ -367,16 +384,35 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { - scheduleInsightsTask()} - disabled={!aiFeatures?.genAiConnectors?.connectors?.length} - isLoading={isSchedulingInsights || isWaitingForInsightsTask} - data-test-subj="significant_events_discover_insights_button" - size="xs" - > - {DISCOVER_INSIGHTS_BUTTON_LABEL} - + + + + scheduleInsightsTask()} + disabled={!aiFeatures?.genAiConnectors?.connectors?.length} + isLoading={isSchedulingInsights || isWaitingForInsightsTask} + data-test-subj="significant_events_discover_insights_button" + size="xs" + > + {DISCOVER_INSIGHTS_BUTTON_LABEL} + + + + + + + diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts index 33ef61e1bafcc..a3ad1fe897ad2 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts @@ -194,3 +194,17 @@ export const RUN_BUTTON_LABEL = i18n.translate( defaultMessage: 'Run', } ); + +export const INSIGHTS_CONNECTOR_POPOVER_ARIA_LABEL = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.insightsConnectorPopoverAriaLabel', + { + defaultMessage: 'Configure discovery connector', + } +); + +export const INSIGHTS_CONNECTOR_POPOVER_TITLE = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.insightsConnectorPopoverTitle', + { + defaultMessage: 'Discovery connector', + } +); diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_insights_discovery_api.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_insights_discovery_api.ts index 96139191c8e43..61ef9c0fad2b7 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_insights_discovery_api.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_insights_discovery_api.ts @@ -22,13 +22,14 @@ export function useInsightsDiscoveryApi() { return useMemo( () => ({ - scheduleInsightsDiscoveryTask: async (streamNames?: string[]) => { + scheduleInsightsDiscoveryTask: async (streamNames?: string[], connectorId?: string) => { await streamsRepositoryClient.fetch('POST /internal/streams/_insights/_task', { signal, params: { body: { action: 'schedule', ...(streamNames && streamNames.length > 0 ? { streamNames } : {}), + ...(connectorId !== undefined && { connectorId }), }, }, }); From 2c586b9a9545f82791799f659213be7ec7a717aa Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 14:57:27 +0200 Subject: [PATCH 05/10] Update x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx Co-authored-by: Achyut Jhunjhunwala --- .../components/streams_view/streams_view.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index 6b7f7c35f1e01..206ce53a8063c 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -272,7 +272,11 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const isStreamActionable = (streamName: string) => { const status = streamOnboardingResultMap[streamName]?.status; return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status); - }; +const isStreamActionable = (streamName: string) => { + const result = streamOnboardingResultMap[streamName]; + if (!result) return false; // status unknown — don't allow action + return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(result.status); +}; const getActionableStreamNames = () => selectedStreams From 2bfada0c5cec403c88cb055990b1171de149127c Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 15:00:01 +0200 Subject: [PATCH 06/10] fix(cr): code review --- .../components/streams_view/streams_view.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index 206ce53a8063c..90a4fb5d38da9 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -270,13 +270,10 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { }, [onboardingStatusUpdateQueue, processStatusUpdateQueue, streamsListFetch.data]); const isStreamActionable = (streamName: string) => { - const status = streamOnboardingResultMap[streamName]?.status; - return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(status); -const isStreamActionable = (streamName: string) => { - const result = streamOnboardingResultMap[streamName]; - if (!result) return false; // status unknown — don't allow action - return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(result.status); -}; + const result = streamOnboardingResultMap[streamName]; + if (!result) return false; + return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(result.status); + }; const getActionableStreamNames = () => selectedStreams From aed0059bbbc4b09c79f60fedff608a3f9c476919 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 15:57:47 +0200 Subject: [PATCH 07/10] fix(cr): handle loading/error of connectors and use useLoadConnectors --- .../streams_view/connector_select_options.tsx | 6 +-- .../insights_connector_popover.tsx | 31 +++++++++------ .../onboarding_config_popover.tsx | 33 ++++++++++------ .../components/streams_view/streams_view.tsx | 20 ++++++---- .../components/streams_view/translations.ts | 7 ++++ .../use_inference_feature_connectors.ts | 38 +++++++------------ .../plugins/shared/streams_app/tsconfig.json | 1 + 7 files changed, 79 insertions(+), 57 deletions(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx index 011dc65ac3588..351867e9b8d3d 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx @@ -6,13 +6,13 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { InferenceConnector } from '@kbn/inference-common'; +import type { AIConnector } from '@kbn/inference-connectors'; import React from 'react'; import { ConnectorIcon } from '../../../../connector_list_button/connector_icon'; -export const buildConnectorSelectOptions = (connectors: InferenceConnector[]) => +export const buildConnectorSelectOptions = (connectors: AIConnector[]) => connectors.map((connector) => ({ - value: connector.connectorId, + value: connector.id, inputDisplay: ( diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx index 24b7784660693..d7868314ed0d1 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx @@ -8,6 +8,7 @@ import { EuiButton, EuiButtonIcon, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -22,6 +23,7 @@ import React, { useCallback } from 'react'; import type { UseInferenceFeatureConnectorsResult } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; import { buildConnectorSelectOptions } from './connector_select_options'; import { + CONNECTOR_LOAD_ERROR, INSIGHTS_CONNECTOR_POPOVER_ARIA_LABEL, INSIGHTS_CONNECTOR_POPOVER_TITLE, RUN_BUTTON_LABEL, @@ -77,19 +79,26 @@ export const InsightsConnectorPopover = ({ > {INSIGHTS_CONNECTOR_POPOVER_TITLE} - {displayConnectorId && connectorOptions.length > 0 && ( + {connectors.error ? ( - - - + + ) : ( + displayConnectorId && + connectorOptions.length > 0 && ( + + + + + + ) )} - {displayConnectorId && connectorOptions.length > 0 && ( + {connectors.error ? ( - - onConnectorChange(step, value)} - disabled={!enabled} - compressed - fullWidth - /> - + + ) : ( + displayConnectorId && + connectorOptions.length > 0 && ( + + + onConnectorChange(step, value)} + disabled={!enabled} + compressed + fullWidth + /> + + + ) )} ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index 90a4fb5d38da9..e330bed451629 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -102,7 +102,7 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { string | undefined >(); const displayDiscoveryConnectorId = - discoveryConnectorOverride ?? discoveryConnectors.resolvedConnector?.connectorId; + discoveryConnectorOverride ?? discoveryConnectors.resolvedConnector?.id; const [onboardingConfig, setOnboardingConfig] = useState({ steps: [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration], @@ -111,10 +111,8 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const displayConnectors = useMemo( () => ({ - features: - onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnector?.connectorId, - queries: - onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnector?.connectorId, + features: onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnector?.id, + queries: onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnector?.id, }), [ onboardingConfig.connectors, @@ -379,7 +377,13 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { queriesConnectors={queriesConnectors} onConfigChange={setOnboardingConfig} onRun={onBulkOnboardStreamsClick} - isRunDisabled={selectedStreams.length === 0} + isRunDisabled={ + selectedStreams.length === 0 || + featuresConnectors.loading || + queriesConnectors.loading || + !!featuresConnectors.error || + !!queriesConnectors.error + } /> @@ -408,7 +412,9 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { isRunDisabled={ !aiFeatures?.genAiConnectors?.connectors?.length || isSchedulingInsights || - isWaitingForInsightsTask + isWaitingForInsightsTask || + discoveryConnectors.loading || + !!discoveryConnectors.error } /> diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts index a3ad1fe897ad2..293cef0d4fc54 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/translations.ts @@ -208,3 +208,10 @@ export const INSIGHTS_CONNECTOR_POPOVER_TITLE = i18n.translate( defaultMessage: 'Discovery connector', } ); + +export const CONNECTOR_LOAD_ERROR = i18n.translate( + 'xpack.streams.significantEventsDiscovery.streamsView.connectorLoadError', + { + defaultMessage: 'Failed to load connectors', + } +); diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts index 2049c5318801b..0a10f740d4236 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts @@ -5,18 +5,13 @@ * 2.0. */ -import type { InferenceConnector } from '@kbn/inference-common'; -import { - INFERENCE_CONNECTORS_INTERNAL_API_PATH, - type InferenceConnectorsApiResponseBody, -} from '@kbn/inference-common'; -import { useAbortController } from '@kbn/react-hooks'; -import useAsync from 'react-use/lib/useAsync'; +import type { AIConnector } from '@kbn/inference-connectors'; +import { useLoadConnectors } from '@kbn/inference-connectors'; import { useKibana } from '../use_kibana'; export interface UseInferenceFeatureConnectorsResult { - resolvedConnector: InferenceConnector | undefined; - allConnectors: InferenceConnector[]; + resolvedConnector: AIConnector | undefined; + allConnectors: AIConnector[]; loading: boolean; error: Error | undefined; } @@ -25,23 +20,18 @@ export function useInferenceFeatureConnectors( featureId: string ): UseInferenceFeatureConnectorsResult { const { core } = useKibana(); - const { signal } = useAbortController(); - const state = useAsync( - () => - core.http.get(INFERENCE_CONNECTORS_INTERNAL_API_PATH, { - query: { featureId }, - version: '1', - signal, - }), - [core.http, featureId, signal] - ); + const query = useLoadConnectors({ + http: core.http, + toasts: core.notifications.toasts, + featureId, + settings: core.settings, + }); return { - // The API returns the feature-matched connector as the first element of `connectors` - resolvedConnector: state.value?.connectors[0], - allConnectors: state.value?.allConnectors ?? [], - loading: state.loading, - error: state.error, + resolvedConnector: query.data?.[0], + allConnectors: query.data ?? [], + loading: query.isLoading, + error: query.error ?? undefined, }; } diff --git a/x-pack/platform/plugins/shared/streams_app/tsconfig.json b/x-pack/platform/plugins/shared/streams_app/tsconfig.json index d7d2058442722..0bf1a578fcfe0 100644 --- a/x-pack/platform/plugins/shared/streams_app/tsconfig.json +++ b/x-pack/platform/plugins/shared/streams_app/tsconfig.json @@ -104,6 +104,7 @@ "@kbn/react-query", "@kbn/timerange", "@kbn/inference-common", + "@kbn/inference-connectors", "@kbn/esql", "@kbn/inference-endpoint-ui-common", "@kbn/stack-connectors-plugin", From cebb40f60966690019765cc73ad5c79ff754309f Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:23:06 +0000 Subject: [PATCH 08/10] Changes from node scripts/regenerate_moon_projects.js --update --- x-pack/platform/plugins/shared/streams_app/moon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/plugins/shared/streams_app/moon.yml b/x-pack/platform/plugins/shared/streams_app/moon.yml index 23af75ff35ad2..95e16d2b44b10 100644 --- a/x-pack/platform/plugins/shared/streams_app/moon.yml +++ b/x-pack/platform/plugins/shared/streams_app/moon.yml @@ -103,6 +103,7 @@ dependsOn: - '@kbn/react-query' - '@kbn/timerange' - '@kbn/inference-common' + - '@kbn/inference-connectors' - '@kbn/esql' - '@kbn/inference-endpoint-ui-common' - '@kbn/stack-connectors-plugin' From 24b42e20d1e2dcbc8d5e870c3f738598c74ade61 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 16:34:12 +0200 Subject: [PATCH 09/10] fix(useloadconnectors): do not use allConnectors from useLoadConnectors --- .../streams_view/connector_select_options.tsx | 6 +-- .../insights_connector_popover.tsx | 22 +++++++---- .../onboarding_config_popover.tsx | 38 ++++++++++--------- .../components/streams_view/streams_view.tsx | 32 ++++++++-------- .../use_inference_feature_connectors.ts | 7 +--- 5 files changed, 56 insertions(+), 49 deletions(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx index 351867e9b8d3d..011dc65ac3588 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx @@ -6,13 +6,13 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { AIConnector } from '@kbn/inference-connectors'; +import type { InferenceConnector } from '@kbn/inference-common'; import React from 'react'; import { ConnectorIcon } from '../../../../connector_list_button/connector_icon'; -export const buildConnectorSelectOptions = (connectors: AIConnector[]) => +export const buildConnectorSelectOptions = (connectors: InferenceConnector[]) => connectors.map((connector) => ({ - value: connector.id, + value: connector.connectorId, inputDisplay: ( diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx index d7868314ed0d1..9da567de187d2 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx @@ -18,9 +18,9 @@ import { useGeneratedHtmlId, } from '@elastic/eui'; import { css } from '@emotion/react'; +import type { InferenceConnector } from '@kbn/inference-common'; import { useBoolean } from '@kbn/react-hooks'; import React, { useCallback } from 'react'; -import type { UseInferenceFeatureConnectorsResult } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; import { buildConnectorSelectOptions } from './connector_select_options'; import { CONNECTOR_LOAD_ERROR, @@ -31,7 +31,8 @@ import { interface InsightsConnectorPopoverProps { displayConnectorId: string | undefined; - connectors: UseInferenceFeatureConnectorsResult; + connectorList: InferenceConnector[]; + connectorError: Error | undefined; onConnectorChange: (connectorId: string) => void; onRun: () => void; isRunDisabled: boolean; @@ -43,7 +44,8 @@ const popoverContentStyle = css` export const InsightsConnectorPopover = ({ displayConnectorId, - connectors, + connectorList, + connectorError, onConnectorChange, onRun, isRunDisabled, @@ -52,7 +54,11 @@ export const InsightsConnectorPopover = ({ const popoverId = useGeneratedHtmlId({ prefix: 'insightsConnectorPopover' }); const selectId = useGeneratedHtmlId({ prefix: 'insightsConnectorSelect' }); - const connectorOptions = buildConnectorSelectOptions(connectors.allConnectors); + const connectorOptions = buildConnectorSelectOptions(connectorList); + const effectiveConnectorId = + displayConnectorId && connectorOptions.some((opt) => opt.value === displayConnectorId) + ? displayConnectorId + : connectorOptions[0]?.value; const handleRun = useCallback(() => { close(); @@ -79,19 +85,19 @@ export const InsightsConnectorPopover = ({ > {INSIGHTS_CONNECTOR_POPOVER_TITLE} - {connectors.error ? ( + {connectorError ? ( - + ) : ( - displayConnectorId && + effectiveConnectorId && connectorOptions.length > 0 && ( void; onConnectorChange: (step: OnboardingStep, connectorId: string) => void; } @@ -66,13 +67,18 @@ const StepRow = ({ label, enabled, displayConnectorId, - connectors, + connectorList, + connectorError, onToggle, onConnectorChange, }: StepRowProps) => { const selectId = useGeneratedHtmlId({ prefix: `onboardingStep_${step}` }); - const connectorOptions = buildConnectorSelectOptions(connectors.allConnectors); + const connectorOptions = buildConnectorSelectOptions(connectorList); + const effectiveConnectorId = + displayConnectorId && connectorOptions.some((opt) => opt.value === displayConnectorId) + ? displayConnectorId + : connectorOptions[0]?.value; return ( @@ -84,19 +90,19 @@ const StepRow = ({ compressed /> - {connectors.error ? ( + {connectorError ? ( - + ) : ( - displayConnectorId && + effectiveConnectorId && connectorOptions.length > 0 && ( onConnectorChange(step, value)} disabled={!enabled} compressed @@ -113,8 +119,8 @@ const StepRow = ({ interface OnboardingConfigPopoverProps { config: OnboardingConfig; displayConnectors: OnboardingConfig['connectors']; - featuresConnectors: UseInferenceFeatureConnectorsResult; - queriesConnectors: UseInferenceFeatureConnectorsResult; + connectorList: InferenceConnector[]; + connectorError: Error | undefined; onConfigChange: (config: OnboardingConfig) => void; onRun: () => void; isRunDisabled: boolean; @@ -127,8 +133,8 @@ const popoverContentStyle = css` export const OnboardingConfigPopover = ({ config, displayConnectors, - featuresConnectors, - queriesConnectors, + connectorList, + connectorError, onConfigChange, onRun, isRunDisabled, @@ -136,11 +142,6 @@ export const OnboardingConfigPopover = ({ const [isOpen, { off: close, toggle }] = useBoolean(false); const popoverId = useGeneratedHtmlId({ prefix: 'onboardingConfigPopover' }); - const connectorsByStep: Record = { - [OnboardingStep.FeaturesIdentification]: featuresConnectors, - [OnboardingStep.QueriesGeneration]: queriesConnectors, - }; - const handleToggle = useCallback( (step: OnboardingStep, enabled: boolean) => { const toggled = enabled ? [...config.steps, step] : config.steps.filter((s) => s !== step); @@ -190,7 +191,8 @@ export const OnboardingConfigPopover = ({ label={label} enabled={config.steps.includes(step)} displayConnectorId={displayConnectors[STEP_CONNECTOR_KEY[step]]} - connectors={connectorsByStep[step]} + connectorList={connectorList} + connectorError={connectorError} onToggle={handleToggle} onConnectorChange={handleConnectorChange} /> diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index e330bed451629..5346ff6dea933 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -102,7 +102,7 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { string | undefined >(); const displayDiscoveryConnectorId = - discoveryConnectorOverride ?? discoveryConnectors.resolvedConnector?.id; + discoveryConnectorOverride ?? discoveryConnectors.resolvedConnectorId; const [onboardingConfig, setOnboardingConfig] = useState({ steps: [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration], @@ -111,13 +111,13 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const displayConnectors = useMemo( () => ({ - features: onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnector?.id, - queries: onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnector?.id, + features: onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnectorId, + queries: onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnectorId, }), [ onboardingConfig.connectors, - featuresConnectors.resolvedConnector, - queriesConnectors.resolvedConnector, + featuresConnectors.resolvedConnectorId, + queriesConnectors.resolvedConnectorId, ] ); @@ -139,6 +139,9 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { >({}); const router = useStreamsAppRouter(); const aiFeatures = useAIFeatures(); + const genAiConnectors = aiFeatures?.genAiConnectors; + const isConnectorCatalogUnavailable = + !genAiConnectors?.connectors?.length || !!genAiConnectors?.loading || !!genAiConnectors?.error; const { scheduleOnboardingTask, cancelOnboardingTask } = useOnboardingApi(); const { scheduleInsightsDiscoveryTask, getInsightsDiscoveryTaskStatus } = useInsightsDiscoveryApi(); @@ -373,16 +376,15 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { @@ -395,7 +397,7 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { scheduleInsightsTask()} - disabled={!aiFeatures?.genAiConnectors?.connectors?.length} + disabled={isConnectorCatalogUnavailable} isLoading={isSchedulingInsights || isWaitingForInsightsTask} data-test-subj="significant_events_discover_insights_button" size="xs" @@ -406,15 +408,15 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts index 0a10f740d4236..e5437d8220b92 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_inference_feature_connectors.ts @@ -5,13 +5,11 @@ * 2.0. */ -import type { AIConnector } from '@kbn/inference-connectors'; import { useLoadConnectors } from '@kbn/inference-connectors'; import { useKibana } from '../use_kibana'; export interface UseInferenceFeatureConnectorsResult { - resolvedConnector: AIConnector | undefined; - allConnectors: AIConnector[]; + resolvedConnectorId: string | undefined; loading: boolean; error: Error | undefined; } @@ -29,8 +27,7 @@ export function useInferenceFeatureConnectors( }); return { - resolvedConnector: query.data?.[0], - allConnectors: query.data ?? [], + resolvedConnectorId: query.data?.[0]?.id, loading: query.isLoading, error: query.error ?? undefined, }; From 9bf67508d59debb203ea9ea4419dc7a626ec6a03 Mon Sep 17 00:00:00 2001 From: Francesco Fagnani Date: Wed, 8 Apr 2026 16:42:39 +0200 Subject: [PATCH 10/10] refactor(useconnectorconfig): add useConnectorConfig --- .../streams_view/connector_select_options.tsx | 17 +++- .../insights_connector_popover.tsx | 17 ++-- .../onboarding_config_popover.tsx | 17 ++-- .../components/streams_view/streams_view.tsx | 76 ++++------------ .../hooks/sig_events/use_connector_config.ts | 86 +++++++++++++++++++ 5 files changed, 139 insertions(+), 74 deletions(-) create mode 100644 x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_connector_config.ts diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx index 011dc65ac3588..b394a8d29c932 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx @@ -10,7 +10,14 @@ import type { InferenceConnector } from '@kbn/inference-common'; import React from 'react'; import { ConnectorIcon } from '../../../../connector_list_button/connector_icon'; -export const buildConnectorSelectOptions = (connectors: InferenceConnector[]) => +export interface ConnectorSelectOption { + value: string; + inputDisplay: React.ReactNode; +} + +export const buildConnectorSelectOptions = ( + connectors: InferenceConnector[] +): ConnectorSelectOption[] => connectors.map((connector) => ({ value: connector.connectorId, inputDisplay: ( @@ -22,3 +29,11 @@ export const buildConnectorSelectOptions = (connectors: InferenceConnector[]) => ), })); + +export const getEffectiveConnectorId = ( + displayConnectorId: string | undefined, + options: ConnectorSelectOption[] +): string | undefined => + displayConnectorId && options.some((opt) => opt.value === displayConnectorId) + ? displayConnectorId + : options[0]?.value; diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx index 9da567de187d2..bf9ac88fd7be2 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx @@ -20,8 +20,8 @@ import { import { css } from '@emotion/react'; import type { InferenceConnector } from '@kbn/inference-common'; import { useBoolean } from '@kbn/react-hooks'; -import React, { useCallback } from 'react'; -import { buildConnectorSelectOptions } from './connector_select_options'; +import React, { useCallback, useMemo } from 'react'; +import { buildConnectorSelectOptions, getEffectiveConnectorId } from './connector_select_options'; import { CONNECTOR_LOAD_ERROR, INSIGHTS_CONNECTOR_POPOVER_ARIA_LABEL, @@ -54,11 +54,14 @@ export const InsightsConnectorPopover = ({ const popoverId = useGeneratedHtmlId({ prefix: 'insightsConnectorPopover' }); const selectId = useGeneratedHtmlId({ prefix: 'insightsConnectorSelect' }); - const connectorOptions = buildConnectorSelectOptions(connectorList); - const effectiveConnectorId = - displayConnectorId && connectorOptions.some((opt) => opt.value === displayConnectorId) - ? displayConnectorId - : connectorOptions[0]?.value; + const connectorOptions = useMemo( + () => buildConnectorSelectOptions(connectorList), + [connectorList] + ); + const effectiveConnectorId = useMemo( + () => getEffectiveConnectorId(displayConnectorId, connectorOptions), + [displayConnectorId, connectorOptions] + ); const handleRun = useCallback(() => { close(); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx index ac51e810632c2..57ad87b53e823 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx @@ -22,8 +22,8 @@ import { css } from '@emotion/react'; import type { InferenceConnector } from '@kbn/inference-common'; import { useBoolean } from '@kbn/react-hooks'; import { OnboardingStep } from '@kbn/streams-schema'; -import React, { useCallback } from 'react'; -import { buildConnectorSelectOptions } from './connector_select_options'; +import React, { useCallback, useMemo } from 'react'; +import { buildConnectorSelectOptions, getEffectiveConnectorId } from './connector_select_options'; import { CONNECTOR_LOAD_ERROR, FEATURES_STEP_LABEL, @@ -74,11 +74,14 @@ const StepRow = ({ }: StepRowProps) => { const selectId = useGeneratedHtmlId({ prefix: `onboardingStep_${step}` }); - const connectorOptions = buildConnectorSelectOptions(connectorList); - const effectiveConnectorId = - displayConnectorId && connectorOptions.some((opt) => opt.value === displayConnectorId) - ? displayConnectorId - : connectorOptions[0]?.value; + const connectorOptions = useMemo( + () => buildConnectorSelectOptions(connectorList), + [connectorList] + ); + const effectiveConnectorId = useMemo( + () => getEffectiveConnectorId(displayConnectorId, connectorOptions), + [displayConnectorId, connectorOptions] + ); return ( diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx index 5346ff6dea933..ee6acdcec34ad 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/streams_view.tsx @@ -18,22 +18,15 @@ import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; import type { OnboardingResult, TaskResult } from '@kbn/streams-schema'; -import { - OnboardingStep, - STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID, - STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID, - STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID, - TaskStatus, -} from '@kbn/streams-schema'; +import { TaskStatus } from '@kbn/streams-schema'; import pMap from 'p-map'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; import type { TableRow } from './utils'; -import { useAIFeatures } from '../../../../../hooks/use_ai_features'; import { useIndexPatternsConfig } from '../../../../../hooks/use_index_patterns_config'; import { useKibana } from '../../../../../hooks/use_kibana'; import { useInsightsDiscoveryApi } from '../../../../../hooks/sig_events/use_insights_discovery_api'; -import { useInferenceFeatureConnectors } from '../../../../../hooks/sig_events/use_inference_feature_connectors'; +import { useConnectorConfig } from '../../../../../hooks/sig_events/use_connector_config'; import type { ScheduleOnboardingOptions } from '../../../../../hooks/use_onboarding_api'; import { useOnboardingApi } from '../../../../../hooks/use_onboarding_api'; import { useStreamsAppRouter } from '../../../../../hooks/use_streams_app_router'; @@ -42,19 +35,15 @@ import { getFormattedError } from '../../../../../util/errors'; import { StreamsAppSearchBar } from '../../../../streams_app_search_bar'; import { useOnboardingStatusUpdateQueue } from '../../hooks/use_onboarding_status_update_queue'; import { InsightsConnectorPopover } from './insights_connector_popover'; -import type { OnboardingConfig } from './onboarding_config_popover'; import { OnboardingConfigPopover } from './onboarding_config_popover'; import { DISCOVER_INSIGHTS_BUTTON_LABEL, - GENERATE_FEATURES_BUTTON_LABEL, - GENERATE_QUERIES_BUTTON_LABEL, getInsightsCompleteToastTitle, INSIGHTS_COMPLETE_TOAST_VIEW_BUTTON, INSIGHTS_SCHEDULING_FAILURE_TITLE, NO_INSIGHTS_TOAST_TITLE, ONBOARDING_FAILURE_TITLE, ONBOARDING_SCHEDULING_FAILURE_TITLE, - RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL, STREAMS_TABLE_SEARCH_ARIA_LABEL, } from './translations'; import { StreamsTreeTable } from './tree_table'; @@ -88,38 +77,20 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { const [isWaitingForInsightsTask, setIsWaitingForInsightsTask] = useState(false); const { filterStreamsByIndexPatterns } = useIndexPatternsConfig(); - const featuresConnectors = useInferenceFeatureConnectors( - STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID - ); - const queriesConnectors = useInferenceFeatureConnectors( - STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID - ); - const discoveryConnectors = useInferenceFeatureConnectors( - STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID - ); - - const [discoveryConnectorOverride, setDiscoveryConnectorOverride] = useState< - string | undefined - >(); - const displayDiscoveryConnectorId = - discoveryConnectorOverride ?? discoveryConnectors.resolvedConnectorId; - - const [onboardingConfig, setOnboardingConfig] = useState({ - steps: [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration], - connectors: {}, - }); - - const displayConnectors = useMemo( - () => ({ - features: onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnectorId, - queries: onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnectorId, - }), - [ - onboardingConfig.connectors, - featuresConnectors.resolvedConnectorId, - queriesConnectors.resolvedConnectorId, - ] - ); + const { + featuresConnectors, + queriesConnectors, + discoveryConnectors, + genAiConnectors, + isConnectorCatalogUnavailable, + discoveryConnectorOverride, + setDiscoveryConnectorOverride, + displayDiscoveryConnectorId, + onboardingConfig, + setOnboardingConfig, + displayConnectors, + dynamicButtonLabel, + } = useConnectorConfig(); const streamsListFetch = useFetchStreams({ select: (result) => { @@ -138,10 +109,6 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { Record> >({}); const router = useStreamsAppRouter(); - const aiFeatures = useAIFeatures(); - const genAiConnectors = aiFeatures?.genAiConnectors; - const isConnectorCatalogUnavailable = - !genAiConnectors?.connectors?.length || !!genAiConnectors?.loading || !!genAiConnectors?.error; const { scheduleOnboardingTask, cancelOnboardingTask } = useOnboardingApi(); const { scheduleInsightsDiscoveryTask, getInsightsDiscoveryTaskStatus } = useInsightsDiscoveryApi(); @@ -317,15 +284,6 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) { cancelOnboardingTask(streamName); }; - const { steps: selectedSteps } = onboardingConfig; - const dynamicButtonLabel = useMemo(() => { - const hasFeatures = selectedSteps.includes(OnboardingStep.FeaturesIdentification); - const hasQueries = selectedSteps.includes(OnboardingStep.QueriesGeneration); - if (hasFeatures && !hasQueries) return GENERATE_FEATURES_BUTTON_LABEL; - if (hasQueries && !hasFeatures) return GENERATE_QUERIES_BUTTON_LABEL; - return RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL; - }, [selectedSteps]); - return ( diff --git a/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_connector_config.ts b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_connector_config.ts new file mode 100644 index 0000000000000..7e0c42757ec22 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/hooks/sig_events/use_connector_config.ts @@ -0,0 +1,86 @@ +/* + * 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 { + OnboardingStep, + STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID, + STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID, + STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID, +} from '@kbn/streams-schema'; +import { useMemo, useState } from 'react'; +import type { OnboardingConfig } from '../../components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover'; +import { + GENERATE_FEATURES_BUTTON_LABEL, + GENERATE_QUERIES_BUTTON_LABEL, + RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL, +} from '../../components/sig_events/significant_events_discovery/components/streams_view/translations'; +import { useAIFeatures } from '../use_ai_features'; +import { useInferenceFeatureConnectors } from './use_inference_feature_connectors'; + +export function useConnectorConfig() { + const featuresConnectors = useInferenceFeatureConnectors( + STREAMS_SIG_EVENTS_KI_EXTRACTION_INFERENCE_FEATURE_ID + ); + const queriesConnectors = useInferenceFeatureConnectors( + STREAMS_SIG_EVENTS_KI_QUERY_GENERATION_INFERENCE_FEATURE_ID + ); + const discoveryConnectors = useInferenceFeatureConnectors( + STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID + ); + + const aiFeatures = useAIFeatures(); + const genAiConnectors = aiFeatures?.genAiConnectors; + const isConnectorCatalogUnavailable = + !genAiConnectors?.connectors?.length || !!genAiConnectors?.loading || !!genAiConnectors?.error; + + const [discoveryConnectorOverride, setDiscoveryConnectorOverride] = useState< + string | undefined + >(); + const displayDiscoveryConnectorId = + discoveryConnectorOverride ?? discoveryConnectors.resolvedConnectorId; + + const [onboardingConfig, setOnboardingConfig] = useState({ + steps: [OnboardingStep.FeaturesIdentification, OnboardingStep.QueriesGeneration], + connectors: {}, + }); + + const displayConnectors = useMemo( + () => ({ + features: onboardingConfig.connectors.features ?? featuresConnectors.resolvedConnectorId, + queries: onboardingConfig.connectors.queries ?? queriesConnectors.resolvedConnectorId, + }), + [ + onboardingConfig.connectors, + featuresConnectors.resolvedConnectorId, + queriesConnectors.resolvedConnectorId, + ] + ); + + const { steps: selectedSteps } = onboardingConfig; + const dynamicButtonLabel = useMemo(() => { + const hasFeatures = selectedSteps.includes(OnboardingStep.FeaturesIdentification); + const hasQueries = selectedSteps.includes(OnboardingStep.QueriesGeneration); + if (hasFeatures && !hasQueries) return GENERATE_FEATURES_BUTTON_LABEL; + if (hasQueries && !hasFeatures) return GENERATE_QUERIES_BUTTON_LABEL; + return RUN_BULK_STREAM_ONBOARDING_BUTTON_LABEL; + }, [selectedSteps]); + + return { + featuresConnectors, + queriesConnectors, + discoveryConnectors, + genAiConnectors, + isConnectorCatalogUnavailable, + discoveryConnectorOverride, + setDiscoveryConnectorOverride, + displayDiscoveryConnectorId, + onboardingConfig, + setOnboardingConfig, + displayConnectors, + dynamicButtonLabel, + }; +}