diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.helpers.tsx b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.helpers.tsx
index 4e245f2e92736..c717a848fdf58 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.helpers.tsx
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.helpers.tsx
@@ -26,10 +26,30 @@ export const createMockLocator = () => ({
export const mockResendRequest = jest.fn();
export const DEFAULT_ENDPOINTS: InferenceAPIConfigResponse[] = [
- { inference_id: '.preconfigured-elser', task_type: 'sparse_embedding' },
- { inference_id: '.preconfigured-e5', task_type: 'text_embedding' },
- { inference_id: 'endpoint-1', task_type: 'text_embedding' },
- { inference_id: 'endpoint-2', task_type: 'sparse_embedding' },
+ {
+ inference_id: '.preconfigured-elser',
+ task_type: 'sparse_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'elser' },
+ },
+ {
+ inference_id: '.preconfigured-e5',
+ task_type: 'text_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'e5' },
+ },
+ {
+ inference_id: 'endpoint-1',
+ task_type: 'text_embedding',
+ service: 'openai',
+ service_settings: { model_id: 'text-embedding-3-large' },
+ },
+ {
+ inference_id: 'endpoint-2',
+ task_type: 'sparse_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'elser' },
+ },
] as InferenceAPIConfigResponse[];
export const defaultProps: SelectInferenceIdProps = {
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx
index fb0d0c8538697..d0de0e7503b3b 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx
@@ -94,31 +94,42 @@ jest.mock('../../../public/application/components/mappings_editor/mappings_state
useDispatch: () => mockDispatch,
}));
-const MockInferenceFlyoutWrapper = ({
- onFlyoutClose,
- onSubmitSuccess,
-}: {
- onFlyoutClose: () => void;
- onSubmitSuccess: (id: string) => void;
- http?: unknown;
- toasts?: unknown;
- isEdit?: boolean;
- enforceAdaptiveAllocations?: boolean;
-}) => (
-
-
-
-
-);
-
-jest.mock('@kbn/inference-endpoint-ui-common', () => ({
- __esModule: true,
- default: MockInferenceFlyoutWrapper,
-}));
+jest.mock('@kbn/inference-endpoint-ui-common', () => {
+ const SERVICE_PROVIDERS = {
+ elastic: { name: 'Elastic' },
+ openai: { name: 'OpenAI' },
+ };
+
+ const MockInferenceFlyoutWrapper = ({
+ onFlyoutClose,
+ onSubmitSuccess,
+ }: {
+ onFlyoutClose: () => void;
+ onSubmitSuccess: (id: string) => void;
+ http?: unknown;
+ toasts?: unknown;
+ isEdit?: boolean;
+ enforceAdaptiveAllocations?: boolean;
+ }) => (
+
+
+
+
+ );
+
+ return {
+ __esModule: true,
+ default: MockInferenceFlyoutWrapper,
+ SERVICE_PROVIDERS,
+ };
+});
jest.mock('../../../public/application/services/api', () => ({
...jest.requireActual('../../../public/application/services/api'),
@@ -414,9 +425,24 @@ describe('SelectInferenceId', () => {
it('SHOULD prioritize .elser-2-elastic over other endpoints IF has enterprise license', async () => {
setupInferenceEndpointsMocks({
data: [
- { inference_id: '.elser-2-elastic', task_type: 'sparse_embedding' },
- { inference_id: '.preconfigured-elser', task_type: 'sparse_embedding' },
- { inference_id: 'endpoint-1', task_type: 'text_embedding' },
+ {
+ inference_id: '.elser-2-elastic',
+ task_type: 'sparse_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'elser-2-elastic' },
+ },
+ {
+ inference_id: '.preconfigured-elser',
+ task_type: 'sparse_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'elser' },
+ },
+ {
+ inference_id: 'endpoint-1',
+ task_type: 'text_embedding',
+ service: 'openai',
+ service_settings: { model_id: 'text-embedding-3-large' },
+ },
] as InferenceAPIConfigResponse[],
});
@@ -432,9 +458,24 @@ describe('SelectInferenceId', () => {
setupInferenceEndpointsMocks({
data: [
- { inference_id: '.elser-2-elastic', task_type: 'sparse_embedding' },
- { inference_id: '.preconfigured-elser', task_type: 'sparse_embedding' },
- { inference_id: 'endpoint-1', task_type: 'text_embedding' },
+ {
+ inference_id: '.elser-2-elastic',
+ task_type: 'sparse_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'elser-2-elastic' },
+ },
+ {
+ inference_id: '.preconfigured-elser',
+ task_type: 'sparse_embedding',
+ service: 'elastic',
+ service_settings: { model_id: 'elser' },
+ },
+ {
+ inference_id: 'endpoint-1',
+ task_type: 'text_embedding',
+ service: 'openai',
+ service_settings: { model_id: 'text-embedding-3-large' },
+ },
] as InferenceAPIConfigResponse[],
});
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx
index 0aa05b25e09c3..8ee72e09b0734 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx
@@ -45,6 +45,10 @@ type SelectInferenceIdContentProps = SelectInferenceIdProps & {
value: string;
};
+interface EndpointOptionData {
+ description: string;
+}
+
export const SelectInferenceId: React.FC = ({
'data-test-subj': dataTestSubj,
}: SelectInferenceIdProps) => {
@@ -116,13 +120,15 @@ const SelectInferenceIdContent: React.FC = ({
* Only includes endpoints compatible with semantic_text (text_embedding and sparse_embedding).
* Includes optimistic updates for newly created endpoints that may not be in the list yet.
*/
- const options: EuiSelectableOption[] = useMemo(() => {
- const selectableOptions: EuiSelectableOption[] =
+ const options: EuiSelectableOption[] = useMemo(() => {
+ const selectableOptions: EuiSelectableOption[] =
compatibleEndpoints?.endpointDefinitions?.map((endpoint) => {
return {
+ key: endpoint.inference_id,
label: endpoint.inference_id,
'data-test-subj': `custom-inference_${endpoint.inference_id}`,
checked: value === endpoint.inference_id ? 'on' : undefined,
+ description: endpoint.description,
disabled: !endpoint.accessible,
append: !endpoint.accessible && endpoint.requiredLicense && (
@@ -150,9 +156,11 @@ const SelectInferenceIdContent: React.FC = ({
const isValueInOptions = selectableOptions.some((option) => option.label === value);
if (value && !isValueInOptions) {
selectableOptions.push({
+ key: value,
label: value,
checked: 'on',
'data-test-subj': `custom-inference_${value}`,
+ description: '',
});
}
return selectableOptions;
@@ -160,6 +168,17 @@ const SelectInferenceIdContent: React.FC = ({
const selectedOptionLabel = options.find((option) => option.checked)?.label;
+ const renderEndpointOption = useCallback((option: EuiSelectableOption) => {
+ return (
+ <>
+ {option.label}
+
+ {option.description}
+
+ >
+ );
+ }, []);
+
/**
* Auto-select default inference endpoint when:
* - No endpoint is currently selected (!value)
@@ -282,7 +301,8 @@ const SelectInferenceIdContent: React.FC = ({
}
)}
>
-
+ id="inferenceEndpointsSelectable"
aria-label={i18n.translate(
'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.selectable.ariaLabel',
{
@@ -307,6 +327,11 @@ const SelectInferenceIdContent: React.FC = ({
onChange={(newOptions) => {
setValue(newOptions.find((option) => option.checked)?.label || '');
}}
+ renderOption={renderEndpointOption}
+ listProps={{
+ isVirtualized: false,
+ }}
+ height={euiTheme.base * 15}
>
{(list, search) => (
<>
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.test.tsx b/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.test.tsx
index 3732d09b5651e..de8100450854d 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.test.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.test.tsx
@@ -17,7 +17,9 @@ import type { Index } from '@kbn/index-management-shared-types';
import { notificationService } from '../../../../services/notification';
import { navigateToIndexDetailsPage, getIndexDetailsLink } from '../../../../services/routing';
-const user = userEvent.setup();
+// EUI context menus keep inactive panels mounted with `pointer-events: none`,
+// which can cause user-event to throw when interacting with menu items.
+const user = userEvent.setup({ pointerEventsCheck: 0, delay: null });
jest.mock('../../../../services/routing', () => ({
...jest.requireActual('../../../../services/routing'),
@@ -299,7 +301,8 @@ describe('IndexActionsContextMenu', () => {
renderWithProviders();
await openContextMenu();
- const openBtn = await screen.findByTestId('openIndexMenuButton');
+ const menu = await screen.findByTestId('indexContextMenu');
+ const openBtn = await within(menu).findByTestId('openIndexMenuButton');
await user.click(openBtn);
@@ -406,15 +409,18 @@ describe('IndexActionsContextMenu', () => {
);
await openContextMenu();
- const overviewBtn = await screen.findByText(/show index overview/i);
+ const menu = await screen.findByTestId('indexContextMenu');
+ const overviewBtn = await within(menu).findByText(/show index overview/i);
await user.click(overviewBtn);
await openContextMenu();
- const settingsBtn = await screen.findByText(/show index settings/i);
+ const menu2 = await screen.findByTestId('indexContextMenu');
+ const settingsBtn = await within(menu2).findByText(/show index settings/i);
await user.click(settingsBtn);
await openContextMenu();
- const mappingBtn = await screen.findByText(/show index mapping/i);
+ const menu3 = await screen.findByTestId('indexContextMenu');
+ const mappingBtn = await within(menu3).findByText(/show index mapping/i);
await user.click(mappingBtn);
expect(navigateToIndexDetailsPage).toHaveBeenCalledTimes(3);
diff --git a/x-pack/platform/plugins/shared/index_management/public/hooks/use_compatible_inference_endpoints.ts b/x-pack/platform/plugins/shared/index_management/public/hooks/use_compatible_inference_endpoints.ts
index fcb9d8e01746f..26b6336a85449 100644
--- a/x-pack/platform/plugins/shared/index_management/public/hooks/use_compatible_inference_endpoints.ts
+++ b/x-pack/platform/plugins/shared/index_management/public/hooks/use_compatible_inference_endpoints.ts
@@ -9,6 +9,7 @@ import { useMemo } from 'react';
import { defaultInferenceEndpoints } from '@kbn/inference-common';
import type { LicenseType } from '@kbn/licensing-types';
import type { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+import { SERVICE_PROVIDERS } from '@kbn/inference-endpoint-ui-common';
import { useLicense } from './use_license';
const COMPATIBLE_TASK_TYPES = ['text_embedding', 'sparse_embedding'] as const;
@@ -28,6 +29,7 @@ interface EndpointDefinition {
requiredLicense: string | undefined;
/** Whether the endpoint is accessible to the current license. Defaults to true if no license requirement is specified. */
accessible: boolean;
+ description: string;
}
interface CompatibleEndpointsData {
defaultInferenceId: string | undefined;
@@ -56,6 +58,11 @@ export const useCompatibleInferenceEndpoints = (
if (!COMPATIBLE_TASK_TYPES.includes(endpoint.task_type as CompatibleTaskType)) {
return;
}
+ const provider = SERVICE_PROVIDERS[endpoint.service];
+ const modelId = endpoint.service_settings.model_id ?? endpoint.service_settings.model;
+ const service = provider?.name ?? endpoint.service;
+ const description = modelId ? `${service} - ${modelId}` : service;
+
const isElserInEis =
endpoint.inference_id === defaultInferenceEndpoints.ELSER_IN_EIS_INFERENCE_ID;
const requiredLicense = INFERENCE_ENDPOINT_LICENSE_MAP[endpoint.inference_id];
@@ -70,10 +77,12 @@ export const useCompatibleInferenceEndpoints = (
defaultInferenceId = endpoint.inference_id;
}
}
+
endpointDefinitions.push({
inference_id: endpoint.inference_id,
requiredLicense,
accessible,
+ description,
});
});
return {