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
deleted file mode 100644
index b394a8d29c932..0000000000000
--- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_select_options.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { 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 interface ConnectorSelectOption {
- value: string;
- inputDisplay: React.ReactNode;
-}
-
-export const buildConnectorSelectOptions = (
- connectors: InferenceConnector[]
-): ConnectorSelectOption[] =>
- connectors.map((connector) => ({
- value: connector.connectorId,
- inputDisplay: (
-
-
-
-
- {connector.name}
-
- ),
- }));
-
-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/connector_sub_panel.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_sub_panel.tsx
new file mode 100644
index 0000000000000..5e683a48b465a
--- /dev/null
+++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/connector_sub_panel.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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 { EuiBadge, EuiSelectable } from '@elastic/eui';
+import type { EuiSelectableOption } from '@elastic/eui';
+import type { InferenceConnector } from '@kbn/inference-common';
+import React, { useCallback, useMemo } from 'react';
+import { ConnectorIcon } from '../../../../connector_list_button/connector_icon';
+import { DEFAULT_MODEL_BADGE_LABEL } from './translations';
+
+interface ConnectorSubPanelProps {
+ connectors: InferenceConnector[];
+ resolvedConnectorId: string | undefined;
+ selectedConnectorId: string | undefined;
+ onSelect: (connectorId: string) => void;
+}
+
+export const ConnectorSubPanel = ({
+ connectors,
+ resolvedConnectorId,
+ selectedConnectorId,
+ onSelect,
+}: ConnectorSubPanelProps) => {
+ const options = useMemo(
+ () =>
+ connectors.map((connector) => ({
+ label: connector.name,
+ key: connector.connectorId,
+ checked: connector.connectorId === selectedConnectorId ? ('on' as const) : undefined,
+ prepend: ,
+ append:
+ connector.connectorId === resolvedConnectorId ? (
+ {DEFAULT_MODEL_BADGE_LABEL}
+ ) : undefined,
+ })),
+ [connectors, selectedConnectorId, resolvedConnectorId]
+ );
+
+ const handleChange = useCallback(
+ (newOptions: EuiSelectableOption[]) => {
+ const selected = newOptions.find((o) => o.checked === 'on');
+ if (selected?.key) onSelect(selected.key);
+ },
+ [onSelect]
+ );
+
+ return (
+
+ {(list) => list}
+
+ );
+};
diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/context_menu_helpers.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/context_menu_helpers.tsx
new file mode 100644
index 0000000000000..a417b5425fe62
--- /dev/null
+++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/context_menu_helpers.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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';
+import { ConnectorSubPanel } from './connector_sub_panel';
+import { MODEL_SELECTION_PANEL_TITLE } from './translations';
+
+export function buildConnectorSelectionPanel({
+ connectors,
+ resolvedConnectorId,
+ selectedConnectorId,
+ onSelect,
+}: {
+ connectors: InferenceConnector[];
+ resolvedConnectorId: string | undefined;
+ selectedConnectorId: string | undefined;
+ onSelect: (connectorId: string) => void;
+}) {
+ return {
+ title: MODEL_SELECTION_PANEL_TITLE,
+ width: 300,
+ content: (
+
+ ),
+ };
+}
+
+export function buildConnectorMenuItem({
+ connector,
+ panelId,
+}: {
+ connector: InferenceConnector | undefined;
+ panelId: number;
+}): { name: React.ReactNode; panel: number } {
+ return {
+ name: (
+
+
+
+
+
+ {connector?.name ?? '—'}
+
+
+ ),
+ panel: panelId,
+ };
+}
diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/context_menu_split_button.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/context_menu_split_button.tsx
new file mode 100644
index 0000000000000..60ae116af17d0
--- /dev/null
+++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/context_menu_split_button.tsx
@@ -0,0 +1,153 @@
+/*
+ * 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 {
+ EuiCallOut,
+ EuiContextMenu,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiSplitButton,
+ useEuiTheme,
+ useGeneratedHtmlId,
+} from '@elastic/eui';
+import type { EuiContextMenuPanelDescriptor } from '@elastic/eui';
+import { useBoolean } from '@kbn/react-hooks';
+import React, { useCallback, useMemo, useState } from 'react';
+import type { ComponentProps } from 'react';
+import { useModelSettingsUrl } from '../../../../../hooks/use_model_settings_url';
+import { MODEL_SETTINGS_LABEL } from './translations';
+
+const modelSettingsMenuName = (
+
+ {MODEL_SETTINGS_LABEL}
+
+
+
+
+);
+
+export interface MenuHelpers {
+ resetMenu: () => void;
+ closeMenu: () => void;
+}
+
+interface ContextMenuSplitButtonProps {
+ primaryLabel: React.ReactNode;
+ primaryIconType?: string;
+ onPrimaryClick: () => void;
+ isPrimaryDisabled?: boolean;
+ primaryDataTestSubj?: string;
+
+ secondaryAriaLabel: string;
+ isSecondaryDisabled?: boolean;
+ secondaryDataTestSubj?: string;
+
+ buildPanels: (helpers: MenuHelpers) => Array>;
+
+ error?: Error;
+ errorTitle?: string;
+
+ color?: ComponentProps['color'];
+ isLoading?: boolean;
+ 'data-test-subj'?: string;
+}
+
+export const ContextMenuSplitButton = ({
+ primaryLabel,
+ primaryIconType,
+ onPrimaryClick,
+ isPrimaryDisabled,
+ primaryDataTestSubj,
+ secondaryAriaLabel,
+ isSecondaryDisabled,
+ secondaryDataTestSubj,
+ buildPanels,
+ error,
+ errorTitle,
+ color,
+ isLoading,
+ 'data-test-subj': dataTestSubj,
+}: ContextMenuSplitButtonProps) => {
+ const { euiTheme } = useEuiTheme();
+ const [isOpen, { off: close, toggle }] = useBoolean(false);
+ const [menuResetKey, setMenuResetKey] = useState(0);
+ const popoverId = useGeneratedHtmlId({ prefix: 'contextMenuSplitButton' });
+ const managementUrl = useModelSettingsUrl();
+
+ const resetMenu = useCallback(() => setMenuResetKey((k) => k + 1), []);
+
+ const closeMenu = useCallback(() => {
+ close();
+ resetMenu();
+ }, [close, resetMenu]);
+
+ const panels = useMemo(() => {
+ const builtPanels = buildPanels({ resetMenu, closeMenu });
+
+ const settingsItems = managementUrl
+ ? [
+ { isSeparator: true as const },
+ {
+ name: modelSettingsMenuName,
+ icon: 'gear' as const,
+ href: managementUrl,
+ target: '_blank',
+ onClick: closeMenu,
+ },
+ ]
+ : [];
+
+ return builtPanels.map((panel, index) => ({
+ ...panel,
+ id: index,
+ ...(index === 0 && panel.items && settingsItems.length > 0
+ ? { items: [...panel.items, ...settingsItems] }
+ : {}),
+ }));
+ }, [buildPanels, resetMenu, closeMenu, managementUrl]);
+
+ const popoverContent = error ? (
+
+ ) : (
+
+ );
+
+ return (
+
+
+ {primaryLabel}
+
+
+
+ );
+};
diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/generate_split_button.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/generate_split_button.tsx
new file mode 100644
index 0000000000000..751be327ae749
--- /dev/null
+++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/generate_split_button.tsx
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { InferenceConnector } from '@kbn/inference-common';
+import React, { useCallback, useMemo } from 'react';
+import {
+ CONNECTOR_LOAD_ERROR,
+ GENERATE_BUTTON_LABEL,
+ GENERATE_FEATURES_BUTTON_LABEL,
+ GENERATE_FEATURES_TOOLTIP,
+ GENERATE_QUERIES_BUTTON_LABEL,
+ GENERATE_QUERIES_TOOLTIP,
+ GENERATE_CONFIG_ARIA_LABEL,
+} from './translations';
+import { buildConnectorMenuItem, buildConnectorSelectionPanel } from './context_menu_helpers';
+import type { OnboardingConfig } from './types';
+import { ContextMenuSplitButton } from './context_menu_split_button';
+import type { MenuHelpers } from './context_menu_split_button';
+
+interface GenerateSplitButtonProps {
+ config: OnboardingConfig;
+ allConnectors: InferenceConnector[];
+ connectorError: Error | undefined;
+ featuresResolvedConnectorId: string | undefined;
+ queriesResolvedConnectorId: string | undefined;
+ onConfigChange: (config: OnboardingConfig) => void;
+ onRun: () => void;
+ onRunFeaturesOnly: () => void;
+ onRunQueriesOnly: () => void;
+ isRunDisabled: boolean;
+ isConfigDisabled: boolean;
+}
+
+export const GenerateSplitButton = ({
+ config,
+ allConnectors,
+ connectorError,
+ featuresResolvedConnectorId,
+ queriesResolvedConnectorId,
+ onConfigChange,
+ onRun,
+ onRunFeaturesOnly,
+ onRunQueriesOnly,
+ isRunDisabled,
+ isConfigDisabled,
+}: GenerateSplitButtonProps) => {
+ const featuresConnector = useMemo(
+ () => allConnectors.find((c) => c.connectorId === config.connectors.features),
+ [allConnectors, config.connectors.features]
+ );
+ const queriesConnector = useMemo(
+ () => allConnectors.find((c) => c.connectorId === config.connectors.queries),
+ [allConnectors, config.connectors.queries]
+ );
+
+ const onSelectFeaturesConnector = useCallback(
+ (connectorId: string) => {
+ onConfigChange({
+ ...config,
+ connectors: { ...config.connectors, features: connectorId },
+ });
+ },
+ [config, onConfigChange]
+ );
+
+ const onSelectQueriesConnector = useCallback(
+ (connectorId: string) => {
+ onConfigChange({
+ ...config,
+ connectors: { ...config.connectors, queries: connectorId },
+ });
+ },
+ [config, onConfigChange]
+ );
+
+ const buildPanels = useCallback(
+ ({ resetMenu, closeMenu }: MenuHelpers) => [
+ {
+ items: [
+ {
+ name: GENERATE_FEATURES_BUTTON_LABEL,
+ onClick: () => {
+ closeMenu();
+ onRunFeaturesOnly();
+ },
+ disabled: isRunDisabled,
+ toolTipContent: GENERATE_FEATURES_TOOLTIP,
+ toolTipProps: { position: 'right' as const },
+ },
+ buildConnectorMenuItem({ connector: featuresConnector, panelId: 1 }),
+ { isSeparator: true as const },
+ {
+ name: GENERATE_QUERIES_BUTTON_LABEL,
+ onClick: () => {
+ closeMenu();
+ onRunQueriesOnly();
+ },
+ disabled: isRunDisabled,
+ toolTipContent: GENERATE_QUERIES_TOOLTIP,
+ toolTipProps: { position: 'right' as const },
+ },
+ buildConnectorMenuItem({ connector: queriesConnector, panelId: 2 }),
+ ],
+ },
+ buildConnectorSelectionPanel({
+ connectors: allConnectors,
+ resolvedConnectorId: featuresResolvedConnectorId,
+ selectedConnectorId: config.connectors.features,
+ onSelect: (connectorId) => {
+ onSelectFeaturesConnector(connectorId);
+ resetMenu();
+ },
+ }),
+ buildConnectorSelectionPanel({
+ connectors: allConnectors,
+ resolvedConnectorId: queriesResolvedConnectorId,
+ selectedConnectorId: config.connectors.queries,
+ onSelect: (connectorId) => {
+ onSelectQueriesConnector(connectorId);
+ resetMenu();
+ },
+ }),
+ ],
+ [
+ isRunDisabled,
+ featuresConnector,
+ queriesConnector,
+ allConnectors,
+ featuresResolvedConnectorId,
+ queriesResolvedConnectorId,
+ config.connectors.features,
+ config.connectors.queries,
+ onSelectFeaturesConnector,
+ onSelectQueriesConnector,
+ onRunFeaturesOnly,
+ onRunQueriesOnly,
+ ]
+ );
+
+ return (
+
+ );
+};
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
deleted file mode 100644
index bf9ac88fd7be2..0000000000000
--- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_connector_popover.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- EuiButton,
- EuiButtonIcon,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiFormRow,
- EuiPopover,
- EuiPopoverTitle,
- EuiSuperSelect,
- 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, useMemo } from 'react';
-import { buildConnectorSelectOptions, getEffectiveConnectorId } from './connector_select_options';
-import {
- CONNECTOR_LOAD_ERROR,
- INSIGHTS_CONNECTOR_POPOVER_ARIA_LABEL,
- INSIGHTS_CONNECTOR_POPOVER_TITLE,
- RUN_BUTTON_LABEL,
-} from './translations';
-
-interface InsightsConnectorPopoverProps {
- displayConnectorId: string | undefined;
- connectorList: InferenceConnector[];
- connectorError: Error | undefined;
- onConnectorChange: (connectorId: string) => void;
- onRun: () => void;
- isRunDisabled: boolean;
-}
-
-const popoverContentStyle = css`
- min-width: 280px;
-`;
-
-export const InsightsConnectorPopover = ({
- displayConnectorId,
- connectorList,
- connectorError,
- onConnectorChange,
- onRun,
- isRunDisabled,
-}: InsightsConnectorPopoverProps) => {
- const [isOpen, { off: close, toggle }] = useBoolean(false);
- const popoverId = useGeneratedHtmlId({ prefix: 'insightsConnectorPopover' });
- const selectId = useGeneratedHtmlId({ prefix: 'insightsConnectorSelect' });
-
- const connectorOptions = useMemo(
- () => buildConnectorSelectOptions(connectorList),
- [connectorList]
- );
- const effectiveConnectorId = useMemo(
- () => getEffectiveConnectorId(displayConnectorId, connectorOptions),
- [displayConnectorId, connectorOptions]
- );
-
- const handleRun = useCallback(() => {
- close();
- onRun();
- }, [close, onRun]);
-
- return (
-
- }
- panelPaddingSize="m"
- >
- {INSIGHTS_CONNECTOR_POPOVER_TITLE}
-
- {connectorError ? (
-
-
-
- ) : (
- effectiveConnectorId &&
- 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/insights_split_button.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_split_button.tsx
new file mode 100644
index 0000000000000..b571f4b269d53
--- /dev/null
+++ b/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/insights_split_button.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { InferenceConnector } from '@kbn/inference-common';
+import React, { useCallback, useMemo } from 'react';
+import {
+ CONNECTOR_LOAD_ERROR,
+ DISCOVER_INSIGHTS_BUTTON_LABEL,
+ DISCOVER_INSIGHTS_CONFIG_ARIA_LABEL,
+} from './translations';
+import { buildConnectorMenuItem, buildConnectorSelectionPanel } from './context_menu_helpers';
+import { ContextMenuSplitButton } from './context_menu_split_button';
+import type { MenuHelpers } from './context_menu_split_button';
+
+interface InsightsSplitButtonProps {
+ allConnectors: InferenceConnector[];
+ connectorError: Error | undefined;
+ resolvedConnectorId: string | undefined;
+ displayConnectorId: string | undefined;
+ onConnectorChange: (connectorId: string) => void;
+ onRun: () => void;
+ isLoading: boolean;
+ isDisabled: boolean;
+}
+
+export const InsightsSplitButton = ({
+ allConnectors,
+ connectorError,
+ resolvedConnectorId,
+ displayConnectorId,
+ onConnectorChange,
+ onRun,
+ isLoading,
+ isDisabled,
+}: InsightsSplitButtonProps) => {
+ const discoveryConnector = useMemo(
+ () => allConnectors.find((c) => c.connectorId === displayConnectorId),
+ [allConnectors, displayConnectorId]
+ );
+
+ const buildPanels = useCallback(
+ ({ resetMenu }: MenuHelpers) => [
+ {
+ items: [buildConnectorMenuItem({ connector: discoveryConnector, panelId: 1 })],
+ },
+ buildConnectorSelectionPanel({
+ connectors: allConnectors,
+ resolvedConnectorId,
+ selectedConnectorId: displayConnectorId,
+ onSelect: (connectorId) => {
+ onConnectorChange(connectorId);
+ resetMenu();
+ },
+ }),
+ ],
+ [discoveryConnector, allConnectors, resolvedConnectorId, displayConnectorId, onConnectorChange]
+ );
+
+ return (
+
+ );
+};
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
deleted file mode 100644
index 57ad87b53e823..0000000000000
--- a/x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/streams_view/onboarding_config_popover.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- EuiButton,
- EuiButtonIcon,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiFormRow,
- EuiPopover,
- EuiPopoverTitle,
- EuiSuperSelect,
- EuiSwitch,
- useGeneratedHtmlId,
-} from '@elastic/eui';
-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, useMemo } from 'react';
-import { buildConnectorSelectOptions, getEffectiveConnectorId } from './connector_select_options';
-import {
- CONNECTOR_LOAD_ERROR,
- 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;
- };
-}
-
-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;
- displayConnectorId: string | undefined;
- connectorList: InferenceConnector[];
- connectorError: Error | undefined;
- onToggle: (step: OnboardingStep, enabled: boolean) => void;
- onConnectorChange: (step: OnboardingStep, connectorId: string) => void;
-}
-
-const StepRow = ({
- step,
- label,
- enabled,
- displayConnectorId,
- connectorList,
- connectorError,
- onToggle,
- onConnectorChange,
-}: StepRowProps) => {
- const selectId = useGeneratedHtmlId({ prefix: `onboardingStep_${step}` });
-
- const connectorOptions = useMemo(
- () => buildConnectorSelectOptions(connectorList),
- [connectorList]
- );
- const effectiveConnectorId = useMemo(
- () => getEffectiveConnectorId(displayConnectorId, connectorOptions),
- [displayConnectorId, connectorOptions]
- );
-
- return (
-
-
- onToggle(step, e.target.checked)}
- compressed
- />
-
- {connectorError ? (
-
-
-
- ) : (
- effectiveConnectorId &&
- connectorOptions.length > 0 && (
-
-
- onConnectorChange(step, value)}
- disabled={!enabled}
- compressed
- fullWidth
- />
-
-
- )
- )}
-
- );
-};
-
-interface OnboardingConfigPopoverProps {
- config: OnboardingConfig;
- displayConnectors: OnboardingConfig['connectors'];
- connectorList: InferenceConnector[];
- connectorError: Error | undefined;
- onConfigChange: (config: OnboardingConfig) => void;
- onRun: () => void;
- isRunDisabled: boolean;
-}
-
-const popoverContentStyle = css`
- min-width: 280px;
-`;
-
-export const OnboardingConfigPopover = ({
- config,
- displayConnectors,
- connectorList,
- connectorError,
- 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);
- 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_CONNECTOR_KEY[step];
- onConfigChange({ ...config, connectors: { ...config.connectors, [key]: newConnectorId } });
- },
- [config, onConfigChange]
- );
-
- const handleRun = useCallback(() => {
- close();
- onRun();
- }, [close, onRun]);
-
- return (
-
- }
- panelPaddingSize="m"
- >
- {ONBOARDING_CONFIG_POPOVER_TITLE}
-
- {STEPS.map(({ step, label }) => (
-
-
-
- ))}
-
-
- {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 ee6acdcec34ad..1f6dcc9bce77e 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
@@ -6,19 +6,12 @@
*/
import type { EuiSearchBarProps, Query } from '@elastic/eui';
-import {
- EuiButton,
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSearchBar,
- EuiText,
-} from '@elastic/eui';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSearchBar, EuiText } from '@elastic/eui';
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, TaskStatus } from '@kbn/streams-schema';
import pMap from 'p-map';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import useAsyncFn from 'react-use/lib/useAsyncFn';
@@ -34,10 +27,9 @@ 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 { OnboardingConfigPopover } from './onboarding_config_popover';
+import { GenerateSplitButton } from './generate_split_button';
+import { InsightsSplitButton } from './insights_split_button';
import {
- DISCOVER_INSIGHTS_BUTTON_LABEL,
getInsightsCompleteToastTitle,
INSIGHTS_COMPLETE_TOAST_VIEW_BUTTON,
INSIGHTS_SCHEDULING_FAILURE_TITLE,
@@ -49,10 +41,6 @@ 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,
@@ -81,15 +69,14 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) {
featuresConnectors,
queriesConnectors,
discoveryConnectors,
- genAiConnectors,
+ allConnectors,
+ connectorError,
isConnectorCatalogUnavailable,
discoveryConnectorOverride,
setDiscoveryConnectorOverride,
displayDiscoveryConnectorId,
onboardingConfig,
setOnboardingConfig,
- displayConnectors,
- dynamicButtonLabel,
} = useConnectorConfig();
const streamsListFetch = useFetchStreams({
@@ -237,44 +224,72 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) {
});
}, [onboardingStatusUpdateQueue, processStatusUpdateQueue, streamsListFetch.data]);
- const isStreamActionable = (streamName: string) => {
- const result = streamOnboardingResultMap[streamName];
- if (!result) return false;
- return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(result.status);
- };
+ const isStreamActionable = useCallback(
+ (streamName: string) => {
+ const result = streamOnboardingResultMap[streamName];
+ if (!result) return false;
+ return ![TaskStatus.InProgress, TaskStatus.BeingCanceled].includes(result.status);
+ },
+ [streamOnboardingResultMap]
+ );
- const getActionableStreamNames = () =>
- selectedStreams
- .filter((item) => isStreamActionable(item.stream.name))
- .map((item) => item.stream.name);
+ const getActionableStreamNames = useCallback(
+ () =>
+ selectedStreams
+ .filter((item) => isStreamActionable(item.stream.name))
+ .map((item) => item.stream.name),
+ [selectedStreams, isStreamActionable]
+ );
- const bulkScheduleOnboardingTask = async (
- streamList: string[],
- options?: ScheduleOnboardingOptions
- ) => {
- try {
- await pMap(
- streamList,
- async (streamName) => {
- await scheduleOnboardingTask(streamName, options);
- },
- { concurrency: 10 }
- );
- } catch (error) {
- toasts.addError(getFormattedError(error), { title: ONBOARDING_SCHEDULING_FAILURE_TITLE });
- }
+ const bulkScheduleOnboardingTask = useCallback(
+ async (streamList: string[], options?: ScheduleOnboardingOptions) => {
+ try {
+ await pMap(
+ streamList,
+ async (streamName) => {
+ await scheduleOnboardingTask(streamName, options);
+ },
+ { concurrency: 10 }
+ );
+ } catch (error) {
+ toasts.addError(getFormattedError(error), { title: ONBOARDING_SCHEDULING_FAILURE_TITLE });
+ }
- streamList.forEach((streamName) => {
- onboardingStatusUpdateQueue.add(streamName);
- });
- processStatusUpdateQueue();
- };
+ streamList.forEach((streamName) => {
+ onboardingStatusUpdateQueue.add(streamName);
+ });
+ processStatusUpdateQueue();
+ },
+ [scheduleOnboardingTask, toasts, onboardingStatusUpdateQueue, processStatusUpdateQueue]
+ );
- const onBulkOnboardStreamsClick = async () => {
+ const onBulkOnboardStreamsClick = useCallback(async () => {
const streamList = getActionableStreamNames();
setSelectedStreams([]);
await bulkScheduleOnboardingTask(streamList, onboardingConfig);
- };
+ }, [getActionableStreamNames, bulkScheduleOnboardingTask, onboardingConfig]);
+
+ const onBulkOnboardStep = useCallback(
+ async (step: OnboardingStep) => {
+ const streamList = getActionableStreamNames();
+ setSelectedStreams([]);
+ await bulkScheduleOnboardingTask(streamList, {
+ steps: [step],
+ connectors: onboardingConfig.connectors,
+ });
+ },
+ [getActionableStreamNames, bulkScheduleOnboardingTask, onboardingConfig.connectors]
+ );
+
+ const onBulkOnboardFeaturesOnly = useCallback(
+ () => onBulkOnboardStep(OnboardingStep.FeaturesIdentification),
+ [onBulkOnboardStep]
+ );
+
+ const onBulkOnboardQueriesOnly = useCallback(
+ () => onBulkOnboardStep(OnboardingStep.QueriesGeneration),
+ [onBulkOnboardStep]
+ );
const onOnboardStreamActionClick = async (streamName: string) => {
await bulkScheduleOnboardingTask([streamName]);
@@ -301,88 +316,53 @@ export function StreamsView({ refreshUnbackedQueriesCount }: StreamsViewProps) {
-
-
-
-
-
-
- {i18n.translate(
- 'xpack.streams.significantEventsDiscovery.streamsTree.streamsCountLabel',
- {
- defaultMessage: '{count} streams',
- values: { count: streamsListFetch.data?.streams.length ?? 0 },
- }
- )}
-
-
-
-
-
- {dynamicButtonLabel}
-
-
-
-
-
-
+
-
-
-
- scheduleInsightsTask()}
- disabled={isConnectorCatalogUnavailable}
- isLoading={isSchedulingInsights || isWaitingForInsightsTask}
- data-test-subj="significant_events_discover_insights_button"
- size="xs"
- >
- {DISCOVER_INSIGHTS_BUTTON_LABEL}
-
-
-
-
-
-
+
+
+
+ {i18n.translate(
+ 'xpack.streams.significantEventsDiscovery.streamsTree.streamsCountLabel',
+ {
+ defaultMessage: '{count} streams',
+ values: { count: streamsListFetch.data?.streams.length ?? 0 },
+ }
+ )}
+
+
+
({
- 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]);
+ useEffect(() => {
+ setOnboardingConfig((prev) => {
+ const features = prev.connectors.features ?? featuresConnectors.resolvedConnectorId;
+ const queries = prev.connectors.queries ?? queriesConnectors.resolvedConnectorId;
+ if (features === prev.connectors.features && queries === prev.connectors.queries) {
+ return prev;
+ }
+ return { ...prev, connectors: { features, queries } };
+ });
+ }, [featuresConnectors.resolvedConnectorId, queriesConnectors.resolvedConnectorId]);
return {
featuresConnectors,
queriesConnectors,
discoveryConnectors,
- genAiConnectors,
+ allConnectors,
+ connectorError,
isConnectorCatalogUnavailable,
discoveryConnectorOverride,
setDiscoveryConnectorOverride,
displayDiscoveryConnectorId,
onboardingConfig,
setOnboardingConfig,
- displayConnectors,
- dynamicButtonLabel,
};
}