Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bfad0ed
feat(onboarding): add onboarding options
cesco-f Apr 7, 2026
4120dae
fix(cr): code review
cesco-f Apr 8, 2026
b6031b4
fix(style): min width for onboarding button
cesco-f Apr 8, 2026
7eca1ae
feat(insights): add connector selector for insights
cesco-f Apr 8, 2026
3080952
first commit
boriskirov Apr 8, 2026
df05cee
getting the latest updates
boriskirov Apr 8, 2026
789027b
first commit
boriskirov Apr 8, 2026
40fb460
Changes from node scripts/lint_ts_projects --fix
kibanamachine Apr 9, 2026
e770668
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Apr 9, 2026
36c9703
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 9, 2026
8e11493
Merge branch 'main' into onboarding-streams-ux-improvements
cesco-f Apr 9, 2026
8781135
fix(cr): code review
cesco-f Apr 9, 2026
5d8876e
refactor(shared component): add context menu split button
cesco-f Apr 9, 2026
675fc5d
fix(eui theme): use euiTheme
cesco-f Apr 9, 2026
548b201
refactor(cr): code review
cesco-f Apr 9, 2026
4b30067
fix(refactor): more refactor
cesco-f Apr 9, 2026
140d096
refactor(extract): extract onSelectFeaturesConnector and onSelectQuer…
cesco-f Apr 9, 2026
66d7cbb
fix: remove Model word from the selector item in the context menu
boriskirov Apr 9, 2026
5607dd4
fix(i18n): remove i18n
cesco-f Apr 9, 2026
f108626
fix:incrase the width prop of the context menu for list readability
boriskirov Apr 9, 2026
3bd3d29
Merge branch 'main' into onboarding-streams-ux-improvements
elasticmachine Apr 10, 2026
b68b63b
chore: small euiselect fix
achyutjhunjhunwala Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<EuiSelectableOption[]>(
() =>
connectors.map((connector) => ({
label: connector.name,
key: connector.connectorId,
checked: connector.connectorId === selectedConnectorId ? ('on' as const) : undefined,
prepend: <ConnectorIcon connectorName={connector.name} />,
append:
connector.connectorId === resolvedConnectorId ? (
<EuiBadge color="hollow">{DEFAULT_MODEL_BADGE_LABEL}</EuiBadge>
) : 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 (
<EuiSelectable singleSelection="always" options={options} onChange={handleChange}>
{(list) => list}
</EuiSelectable>
);
};
Original file line number Diff line number Diff line change
@@ -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: (
<ConnectorSubPanel
connectors={connectors}
resolvedConnectorId={resolvedConnectorId}
selectedConnectorId={selectedConnectorId}
onSelect={onSelect}
/>
),
};
}

export function buildConnectorMenuItem({
connector,
panelId,
}: {
connector: InferenceConnector | undefined;
panelId: number;
}): { name: React.ReactNode; panel: number } {
return {
name: (
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<ConnectorIcon connectorName={connector?.name} />
</EuiFlexItem>
<EuiFlexItem className="eui-textTruncate" css={{ minWidth: 0 }}>
{connector?.name ?? '—'}
</EuiFlexItem>
</EuiFlexGroup>
),
panel: panelId,
};
}
Original file line number Diff line number Diff line change
@@ -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 = (
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem>{MODEL_SETTINGS_LABEL}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type="popout" size="s" color="subdued" aria-hidden={true} />
</EuiFlexItem>
</EuiFlexGroup>
);

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<Omit<EuiContextMenuPanelDescriptor, 'id'>>;

error?: Error;
errorTitle?: string;

color?: ComponentProps<typeof EuiSplitButton>['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 ? (
<EuiCallOut
announceOnMount
color="danger"
size="s"
title={errorTitle}
css={{ margin: euiTheme.size.s }}
/>
) : (
<EuiContextMenu key={menuResetKey} initialPanelId={0} panels={panels} />
);

return (
<EuiSplitButton size="m" color={color} isLoading={isLoading} data-test-subj={dataTestSubj}>
<EuiSplitButton.ActionPrimary
onClick={onPrimaryClick}
isDisabled={isPrimaryDisabled}
iconType={primaryIconType}
data-test-subj={primaryDataTestSubj}
>
{primaryLabel}
</EuiSplitButton.ActionPrimary>
<EuiSplitButton.ActionSecondary
iconType="arrowDown"
aria-label={secondaryAriaLabel}
isDisabled={isSecondaryDisabled}
data-test-subj={secondaryDataTestSubj}
onClick={toggle}
popoverProps={{
id: popoverId,
isOpen,
closePopover: closeMenu,
anchorPosition: 'downRight',
panelPaddingSize: 'none',
children: popoverContent,
}}
/>
</EuiSplitButton>
);
};
Loading
Loading