diff --git a/config/serverless.es.yml b/config/serverless.es.yml index eafa7f3113395..4b0d416bacdfc 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -113,7 +113,7 @@ data_visualizer.resultLinks.fileBeat.enabled: false xpack.searchPlayground.ui.enabled: true # Search InferenceEndpoints -xpack.searchInferenceEndpoints.ui.enabled: false +xpack.searchInferenceEndpoints.ui.enabled: true # Search Notebooks xpack.search.notebooks.catalog.url: https://elastic-enterprise-search.s3.us-east-2.amazonaws.com/serverless/catalog.json diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index a31e1f1641e8b..44bd57ed8f3d1 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -1001,5 +1001,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D context: `${KIBANA_DOCS}playground-context.html`, hiddenFields: `${KIBANA_DOCS}playground-query.html#playground-hidden-fields`, }, + inferenceManagement: { + inferenceAPIDocumentation: `${ELASTIC_WEBSITE_URL}docs/api/doc/elasticsearch/operation/operation-inference-put`, + }, }); }; diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index ac0f66d83b705..a344d2d694c05 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -676,6 +676,9 @@ export interface DocLinks { readonly context: string; readonly hiddenFields: string; }; + readonly inferenceManagement: { + readonly inferenceAPIDocumentation: string; + }; } export type BuildFlavor = 'serverless' | 'traditional'; diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 0c13a772862b8..2603ea3d89018 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -211,7 +211,7 @@ export const SEARCH_RELEVANCE_PLUGIN = { DESCRIPTION: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.description', { defaultMessage: 'Manage your inference endpoints for semantic search and AI use cases.', }), - URL: '/app/enterprise_search/relevance', + URL: '/app/elasticsearch/relevance', LOGO: 'logoEnterpriseSearch', SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', }; diff --git a/x-pack/plugins/enterprise_search/common/locators/index.ts b/x-pack/plugins/enterprise_search/common/locators/index.ts index 0f9d2060cd829..35c1d43b3b30a 100644 --- a/x-pack/plugins/enterprise_search/common/locators/index.ts +++ b/x-pack/plugins/enterprise_search/common/locators/index.ts @@ -6,14 +6,17 @@ */ import type { SharePluginSetup } from '@kbn/share-plugin/public'; +import { SerializableRecord } from '@kbn/utility-types'; import { CreateIndexLocatorDefinition, type CreateIndexLocatorParams, } from './create_index_locator'; +import { SearchInferenceEndpointLocatorDefinition } from './inference_locator'; import { PlaygroundLocatorDefinition, type PlaygroundLocatorParams } from './playground_locator'; export function registerLocators(share: SharePluginSetup) { share.url.locators.create(new CreateIndexLocatorDefinition()); share.url.locators.create(new PlaygroundLocatorDefinition()); + share.url.locators.create(new SearchInferenceEndpointLocatorDefinition()); } diff --git a/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx b/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx new file mode 100644 index 0000000000000..f20d628bf1899 --- /dev/null +++ b/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx @@ -0,0 +1,30 @@ +/* + * 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 { LocatorDefinition } from '@kbn/share-plugin/common'; +import type { SharePluginSetup } from '@kbn/share-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; + +import { SEARCH_RELEVANCE_PLUGIN } from '../constants'; + +export function registerLocators(share: SharePluginSetup) { + share.url.locators.create(new SearchInferenceEndpointLocatorDefinition()); +} + +export class SearchInferenceEndpointLocatorDefinition + implements LocatorDefinition +{ + public readonly getLocation = async () => { + return { + app: SEARCH_RELEVANCE_PLUGIN.ID, + path: '/inference_endpoints', + state: {}, + }; + }; + + public readonly id = 'SEARCH_INFERENCE_ENDPOINTS'; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 3305e92dd8d9e..a6cbf56691735 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -101,7 +101,7 @@ const baseNavItems = [ items: [ { 'data-test-subj': 'searchSideNav-InferenceEndpoints', - href: '/app/enterprise_search/relevance/inference_endpoints', + href: '/app/elasticsearch/relevance/inference_endpoints', id: 'inference_endpoints', items: undefined, name: 'Inference Endpoints', @@ -205,7 +205,7 @@ const mockNavLinks = [ { id: 'searchInferenceEndpoints:inferenceEndpoints', title: 'Inference Endpoints', - url: '/app/enterprise_search/relevance/inference_endpoints', + url: '/app/elasticsearch/relevance/inference_endpoints', }, { id: 'appSearch:engines', diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index 5a97dadc870cb..4badcc04540b1 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -727,6 +727,11 @@ describe('', () => { isActive: true, hasAtLeast: jest.fn((type) => true), }; + const INFERENCE_LOCATOR = 'SEARCH_INFERENCE_ENDPOINTS'; + const createMockLocator = (id: string) => ({ + useUrl: jest.fn().mockReturnValue('https://redirect.me/to/inference_endpoints'), + }); + const mockInferenceManagementLocator = createMockLocator(INFERENCE_LOCATOR); beforeEach(async () => { httpRequestsMockHelpers.setInferenceModels({ data: [ @@ -750,7 +755,9 @@ describe('', () => { docLinks: { links: { ml: '', - enterpriseSearch: '', + inferenceManagement: { + inferenceAPIDocumentation: 'https://abc.com/inference-api-create', + }, }, }, core: { @@ -819,6 +826,20 @@ describe('', () => { }, }, }, + share: { + url: { + locators: { + get: jest.fn((id) => { + switch (id) { + case INFERENCE_LOCATOR: + return mockInferenceManagementLocator; + default: + throw new Error(`Unknown locator id: ${id}`); + } + }), + }, + }, + }, }, }, }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx index 2fb9165e8fd10..1c9633f829bc3 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx @@ -20,6 +20,11 @@ import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; const createInferenceEndpointMock = jest.fn(); const mockDispatch = jest.fn(); +const INFERENCE_LOCATOR = 'SEARCH_INFERENCE_ENDPOINTS'; +const createMockLocator = (id: string) => ({ + useUrl: jest.fn().mockReturnValue('https://redirect.me/to/inference_endpoints'), +}); +const mockInferenceManagementLocator = createMockLocator(INFERENCE_LOCATOR); jest.mock('../../../public/application/app_context', () => ({ useAppContext: jest.fn().mockReturnValue({ @@ -33,8 +38,8 @@ jest.mock('../../../public/application/app_context', () => ({ }, docLinks: { links: { - enterpriseSearch: { - inferenceApiCreate: 'https://abc.com/inference-api-create', + inferenceManagement: { + inferenceAPIDocumentation: 'https://abc.com/inference-api-create', }, }, }, @@ -47,6 +52,20 @@ jest.mock('../../../public/application/app_context', () => ({ }, }, }, + share: { + url: { + locators: { + get: jest.fn((id) => { + switch (id) { + case INFERENCE_LOCATOR: + return mockInferenceManagementLocator; + default: + throw new Error(`Unknown locator id: ${id}`); + } + }), + }, + }, + }, }, }), })); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx index a9c54aee80360..51cd2b5f14789 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx @@ -80,13 +80,15 @@ const SelectInferenceIdContent: React.FC = ({ value, }) => { const { - core: { application, http }, + core: { application }, docLinks, - plugins: { ml }, + plugins: { ml, share }, } = useAppContext(); const config = getFieldConfig('inference_id'); - const inferenceEndpointsPageLink = `${http.basePath.get()}/app/enterprise_search/relevance/inference_endpoints`; + const inferenceEndpointsPageLink = share?.url.locators + .get('SEARCH_INFERENCE_ENDPOINTS') + ?.useUrl({}); const [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible] = useState(false); const [availableTrainedModels, setAvailableTrainedModels] = useState< @@ -224,24 +226,28 @@ const SelectInferenceIdContent: React.FC = ({ panelPaddingSize="m" closePopover={() => setIsInferencePopoverVisible(!isInferencePopoverVisible)} > - - { - application.navigateToUrl(inferenceEndpointsPageLink); - }} - > - {i18n.translate( - 'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.manageInferenceEndpointButton', - { - defaultMessage: 'Manage Inference Endpoints', - } - )} - - + {inferenceEndpointsPageLink && ( + + { + e.preventDefault(); + application.navigateToUrl(inferenceEndpointsPageLink); + }} + > + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.manageInferenceEndpointButton', + { + defaultMessage: 'Manage Inference Endpoints', + } + )} + + + )} @@ -292,7 +298,7 @@ const SelectInferenceIdContent: React.FC = ({ } size="s"> diff --git a/x-pack/plugins/search_inference_endpoints/common/doc_links.ts b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts index bb426180a36e8..483803e970d82 100644 --- a/x-pack/plugins/search_inference_endpoints/common/doc_links.ts +++ b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts @@ -15,7 +15,7 @@ class InferenceEndpointsDocLinks { constructor() {} setDocLinks(newDocLinks: DocLinks) { - this.createInferenceEndpoint = newDocLinks.enterpriseSearch.inferenceApiCreate; + this.createInferenceEndpoint = newDocLinks.inferenceManagement.inferenceAPIDocumentation; this.semanticSearchElser = newDocLinks.enterpriseSearch.elser; this.semanticSearchE5 = newDocLinks.enterpriseSearch.e5Model; } diff --git a/x-pack/plugins/search_inference_endpoints/common/translations.ts b/x-pack/plugins/search_inference_endpoints/common/translations.ts index 0f1aa4a8abcfc..8b63725c59f96 100644 --- a/x-pack/plugins/search_inference_endpoints/common/translations.ts +++ b/x-pack/plugins/search_inference_endpoints/common/translations.ts @@ -26,49 +26,6 @@ export const MANAGE_INFERENCE_ENDPOINTS_LABEL = i18n.translate( } ); -export const CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription', - { - defaultMessage: - "Inference endpoints enable you to perform inference tasks using NLP models provided by third-party services or Elastic's built-in models like ELSER and E5. Set up tasks such as text embedding, completions, reranking, and more by using the Create Inference API.", - } -); - -export const START_WITH_PREPARED_ENDPOINTS_LABEL = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel', - { - defaultMessage: 'Learn more about built-in NLP models:', - } -); - -export const ELSER_TITLE = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle', - { - defaultMessage: 'ELSER', - } -); - -export const LEARN_HOW_TO_CREATE_INFERENCE_ENDPOINTS_LINK = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints', - { - defaultMessage: 'Learn how to create inference endpoints', - } -); - -export const SEMANTIC_SEARCH_WITH_ELSER_LINK = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser', - { - defaultMessage: 'Semantic search with ELSER', - } -); - -export const SEMANTIC_SEARCH_WITH_E5_LINK = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5', - { - defaultMessage: 'Semantic search with E5 Multilingual', - } -); - export const VIEW_YOUR_MODELS_LINK = i18n.translate( 'xpack.searchInferenceEndpoints.viewYourModels', { @@ -83,25 +40,6 @@ export const API_DOCUMENTATION_LINK = i18n.translate( } ); -export const ELSER_DESCRIPTION = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription', - { - defaultMessage: "ELSER is Elastic's sparse vector NLP model for semantic search in English.", - } -); - -export const E5_TITLE = i18n.translate('xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title', { - defaultMessage: 'E5 Multilingual', -}); - -export const E5_DESCRIPTION = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description', - { - defaultMessage: - 'E5 is a third-party NLP model that enables you to perform multilingual semantic search by using dense vector representations.', - } -); - export const ERROR_TITLE = i18n.translate('xpack.searchInferenceEndpoints.inferenceId.errorTitle', { defaultMessage: 'Error adding inference endpoint', }); diff --git a/x-pack/plugins/search_inference_endpoints/kibana.jsonc b/x-pack/plugins/search_inference_endpoints/kibana.jsonc index f535a9df27e99..25b7b391b955a 100644 --- a/x-pack/plugins/search_inference_endpoints/kibana.jsonc +++ b/x-pack/plugins/search_inference_endpoints/kibana.jsonc @@ -24,6 +24,7 @@ "optionalPlugins": [ "cloud", "console", + "serverless" ], "requiredBundles": [ "kibanaReact" diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts index 7ce1e578f1db0..931994c46afca 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts @@ -34,3 +34,4 @@ export const DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE: AllInferenceEndpointsTable }; export const PIPELINE_URL = 'ingest/ingest_pipelines'; +export const SERVERLESS_INDEX_MANAGEMENT_URL = 'index_details'; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx index 790bb5ec09913..371b204e0acd6 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx @@ -33,6 +33,7 @@ interface UseFilterParams { options: MultiSelectFilterOption[]; renderOption?: (option: MultiSelectFilterOption) => React.ReactNode; selectedOptionKeys?: string[]; + dataTestSubj?: string; } export const MultiSelectFilter: React.FC = ({ @@ -41,6 +42,7 @@ export const MultiSelectFilter: React.FC = ({ options: rawOptions, selectedOptionKeys = [], renderOption, + dataTestSubj, }) => { const { euiTheme } = useEuiTheme(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -55,7 +57,7 @@ export const MultiSelectFilter: React.FC = ({ ); return ( - + = ({ optionKeys, onChange }) options={options} renderOption={(option) => option.label} selectedOptionKeys={optionKeys} + dataTestSubj="service-field-endpoints" /> ); }; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx index e9c32503dba73..071069a880b3c 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx @@ -38,6 +38,7 @@ export const TaskTypeFilter: React.FC = ({ optionKeys, onChange }) => { options={options} renderOption={(option) => option.label} selectedOptionKeys={optionKeys} + dataTestSubj="type-field-endpoints" /> ); }; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.test.tsx new file mode 100644 index 0000000000000..bfdc1edd31bd6 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.test.tsx @@ -0,0 +1,48 @@ +/* + * 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 { render, fireEvent, screen } from '@testing-library/react'; +import React from 'react'; + +import { IndexItem } from './index_item'; +import { InferenceUsageInfo } from '../../../../types'; +import { useKibana } from '../../../../../../hooks/use_kibana'; + +jest.mock('../../../../../../hooks/use_kibana'); +const mockUseKibana = useKibana as jest.Mock; +const mockNavigateToApp = jest.fn(); + +describe('Index Item', () => { + const item: InferenceUsageInfo = { + id: 'index-1', + type: 'Index', + }; + beforeEach(() => { + mockUseKibana.mockReturnValue({ + services: { + application: { + navigateToApp: mockNavigateToApp, + }, + }, + }); + + render(); + }); + + it('renders', () => { + expect(screen.getByText('index-1')).toBeInTheDocument(); + expect(screen.getByText('Index')).toBeInTheDocument(); + }); + + it('opens index in a new tab', () => { + fireEvent.click(screen.getByRole('button')); + expect(mockNavigateToApp).toHaveBeenCalledWith('enterpriseSearchContent', { + openInNewTab: true, + path: 'search_indices/index-1', + }); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.tsx similarity index 77% rename from x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.tsx rename to x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.tsx index 577b9f8aa0e29..a62a9b9f3caed 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.tsx @@ -16,34 +16,35 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import { ENTERPRISE_SEARCH_CONTENT_APP_ID } from '@kbn/deeplinks-search'; -import { MANAGEMENT_APP_ID } from '@kbn/deeplinks-management/constants'; + +import { SEARCH_INDICES } from '@kbn/deeplinks-search/constants'; import { useKibana } from '../../../../../../hooks/use_kibana'; import { InferenceUsageInfo } from '../../../../types'; -import { PIPELINE_URL } from '../../../../constants'; +import { SERVERLESS_INDEX_MANAGEMENT_URL } from '../../../../constants'; interface UsageProps { usageItem: InferenceUsageInfo; } -export const UsageItem: React.FC = ({ usageItem }) => { +export const IndexItem: React.FC = ({ usageItem }) => { const { - services: { application }, + services: { application, serverless }, } = useKibana(); - const handleNavigateToIndex = () => { - if (usageItem.type === 'Index') { - application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { - path: `search_indices/${usageItem.id}`, + const navigateToIndex = useCallback(() => { + if (serverless) { + application?.navigateToApp(SEARCH_INDICES, { + path: `${SERVERLESS_INDEX_MANAGEMENT_URL}/${usageItem.id}/data`, openInNewTab: true, }); - } else if (usageItem.type === 'Pipeline') { - application?.navigateToApp(MANAGEMENT_APP_ID, { - path: `${PIPELINE_URL}?pipeline=${usageItem.id}`, + } else { + application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { + path: `search_indices/${usageItem.id}`, openInNewTab: true, }); } - }; + }, [application, serverless, usageItem.id]); return ( @@ -62,7 +63,7 @@ export const UsageItem: React.FC = ({ usageItem }) => { - + diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx index d42b0f6735252..05aaaa8bb9eaf 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx @@ -10,7 +10,8 @@ import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InferenceUsageInfo } from '../../../../types'; import * as i18n from '../delete/confirm_delete_endpoint/translations'; -import { UsageItem } from './usage_item'; +import { IndexItem } from './index_item'; +import { PipelineItem } from './pipeline_item'; interface ListUsageResultsProps { list: InferenceUsageInfo[]; @@ -35,9 +36,13 @@ export const ListUsageResults: React.FC = ({ list }) => { {list .filter((item) => item.id.toLowerCase().includes(term.toLowerCase())) - .map((item, id) => ( - - ))} + .map((item, id) => { + if (item.type === 'Pipeline') { + return ; + } else { + return ; + } + })} ); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.test.tsx similarity index 59% rename from x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.test.tsx rename to x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.test.tsx index 6c6899c71922d..8a1bc7a78cab2 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.test.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.test.tsx @@ -8,7 +8,7 @@ import { render, fireEvent, screen } from '@testing-library/react'; import React from 'react'; -import { UsageItem } from './usage_item'; +import { PipelineItem } from './pipeline_item'; import { InferenceUsageInfo } from '../../../../types'; import { useKibana } from '../../../../../../hooks/use_kibana'; @@ -16,7 +16,11 @@ jest.mock('../../../../../../hooks/use_kibana'); const mockUseKibana = useKibana as jest.Mock; const mockNavigateToApp = jest.fn(); -describe('UsageItem', () => { +describe('Pipeline item', () => { + const item: InferenceUsageInfo = { + id: 'pipeline-1', + type: 'Pipeline', + }; beforeEach(() => { mockUseKibana.mockReturnValue({ services: { @@ -25,41 +29,10 @@ describe('UsageItem', () => { }, }, }); - }); - - describe('index', () => { - const item: InferenceUsageInfo = { - id: 'index-1', - type: 'Index', - }; - - beforeEach(() => { - render(); - }); - - it('renders', () => { - expect(screen.getByText('index-1')).toBeInTheDocument(); - expect(screen.getByText('Index')).toBeInTheDocument(); - }); - - it('opens index in a new tab', () => { - fireEvent.click(screen.getByRole('button')); - expect(mockNavigateToApp).toHaveBeenCalledWith('enterpriseSearchContent', { - openInNewTab: true, - path: 'search_indices/index-1', - }); - }); + render(); }); describe('pipeline', () => { - const item: InferenceUsageInfo = { - id: 'pipeline-1', - type: 'Pipeline', - }; - - beforeEach(() => { - render(); - }); it('renders', () => { expect(screen.getByText('pipeline-1')).toBeInTheDocument(); expect(screen.getByText('Pipeline')).toBeInTheDocument(); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.tsx new file mode 100644 index 0000000000000..1a2e8ead29080 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.tsx @@ -0,0 +1,69 @@ +/* + * 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, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiText, + EuiTextTruncate, + EuiIcon, + EuiSpacer, +} from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { MANAGEMENT_APP_ID } from '@kbn/deeplinks-management/constants'; + +import { useKibana } from '../../../../../../hooks/use_kibana'; +import { InferenceUsageInfo } from '../../../../types'; +import { PIPELINE_URL } from '../../../../constants'; + +interface UsageProps { + usageItem: InferenceUsageInfo; +} +export const PipelineItem: React.FC = ({ usageItem }) => { + const { + services: { application }, + } = useKibana(); + const navigateToPipeline = useCallback(() => { + application?.navigateToApp(MANAGEMENT_APP_ID, { + path: `${PIPELINE_URL}?pipeline=${usageItem.id}`, + openInNewTab: true, + }); + }, [application, usageItem.id]); + + return ( + + + + + + + + + + + + {usageItem.type} + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx index 33d7a4dae891f..cab278f5e1ed9 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx @@ -19,6 +19,7 @@ import { euiThemeVars } from '@kbn/ui-theme'; import { css } from '@emotion/react'; import { ENTERPRISE_SEARCH_CONTENT_APP_ID } from '@kbn/deeplinks-search'; +import { SEARCH_INDICES } from '@kbn/deeplinks-search/constants'; import { InferenceUsageInfo } from '../../../../types'; import { useKibana } from '../../../../../../hooks/use_kibana'; import { RenderMessageWithIcon } from './render_message_with_icon'; @@ -37,13 +38,19 @@ export const ScanUsageResults: React.FC = ({ onIgnoreWarningCheckboxChange, }) => { const { - services: { application }, + services: { application, serverless }, } = useKibana(); const handleNavigateToIndexManagement = () => { - application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { - path: 'search_indices', - openInNewTab: true, - }); + if (serverless) { + application?.navigateToApp(SEARCH_INDICES, { + openInNewTab: true, + }); + } else { + application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { + path: `search_indices`, + openInNewTab: true, + }); + } }; return ( diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx index 06bd585c0eb28..345f0f81b0927 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx @@ -86,6 +86,7 @@ export const ConfirmDeleteEndpointModal: React.FC {inferenceEndpoint.endpoint} diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx index 4cf4b3112396d..0ea17fa6408a0 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx @@ -52,6 +52,7 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) { field: 'endpoint', name: i18n.ENDPOINT, + 'data-test-subj': 'endpointCell', render: (endpoint: string) => { if (endpoint) { return ; @@ -65,6 +66,7 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) { field: 'provider', name: i18n.SERVICE_PROVIDER, + 'data-test-subj': 'providerCell', render: (provider: InferenceAPIConfigResponse) => { if (provider) { return ; @@ -78,6 +80,7 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) { field: 'type', name: i18n.TASK_TYPE, + 'data-test-subj': 'typeCell', render: (type: TaskTypes) => { if (type) { return ; @@ -149,6 +152,7 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) onChange={handleTableChange} pagination={pagination} sorting={sorting} + data-test-subj="inferenceEndpointTable" /> diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss deleted file mode 100644 index b85859948b0be..0000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss +++ /dev/null @@ -1,3 +0,0 @@ -.addEmptyPrompt { - max-width: 860px; -} \ No newline at end of file diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx deleted file mode 100644 index 755c1f0a1de54..0000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx +++ /dev/null @@ -1,47 +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 React from 'react'; -import { screen } from '@testing-library/react'; -import { AddEmptyPrompt } from './add_empty_prompt'; - -import { renderReactTestingLibraryWithI18n as render } from '@kbn/test-jest-helpers'; -import '@testing-library/jest-dom'; - -describe('When empty prompt is loaded', () => { - beforeEach(() => { - render(); - }); - - it('should display the description for creation of the first inference endpoint', () => { - expect( - screen.getByText( - /Inference endpoints enable you to perform inference tasks using NLP models provided by third-party services/ - ) - ).toBeInTheDocument(); - }); - - it('should have a learn-more link', () => { - const learnMoreLink = screen.getByTestId('learn-how-to-create-inference-endpoints'); - expect(learnMoreLink).toBeInTheDocument(); - }); - - it('should have a view-your-models link', () => { - const learnMoreLink = screen.getByTestId('view-your-models'); - expect(learnMoreLink).toBeInTheDocument(); - }); - - it('should have a semantic-search-with-elser link', () => { - const learnMoreLink = screen.getByTestId('semantic-search-with-elser'); - expect(learnMoreLink).toBeInTheDocument(); - }); - - it('should have a semantic-search-with-e5 link', () => { - const learnMoreLink = screen.getByTestId('semantic-search-with-e5'); - expect(learnMoreLink).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx deleted file mode 100644 index 782994975ada4..0000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx +++ /dev/null @@ -1,103 +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 React from 'react'; - -import { - EuiPageTemplate, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiSpacer, - EuiLink, -} from '@elastic/eui'; -import { docLinks } from '../../../common/doc_links'; - -import * as i18n from '../../../common/translations'; - -import inferenceEndpoint from '../../assets/images/inference_endpoint.svg'; - -import { EndpointPrompt } from './endpoint_prompt'; -import { useTrainedModelPageUrl } from '../../hooks/use_trained_model_page_url'; - -import './add_empty_prompt.scss'; - -export const AddEmptyPrompt: React.FC = () => { - const trainedModelPageUrl = useTrainedModelPageUrl(); - - return ( - } - title={

{i18n.INFERENCE_ENDPOINT_LABEL}

} - body={ - - - {i18n.CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION} - - - - {i18n.LEARN_HOW_TO_CREATE_INFERENCE_ENDPOINTS_LINK} - - - - - {i18n.VIEW_YOUR_MODELS_LINK} - - - - } - footer={ - - - {i18n.START_WITH_PREPARED_ENDPOINTS_LABEL} - - - - - - {i18n.SEMANTIC_SEARCH_WITH_ELSER_LINK} -
- } - /> - - - - {i18n.SEMANTIC_SEARCH_WITH_E5_LINK} - - } - /> - - - - } - /> - ); -}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/endpoint_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/endpoint_prompt.tsx deleted file mode 100644 index aa6ff9d582e10..0000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/endpoint_prompt.tsx +++ /dev/null @@ -1,27 +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 React from 'react'; -import { EuiCard } from '@elastic/eui'; - -interface EndpointPromptProps { - title: string; - description: string; - footer: React.ReactElement; -} - -export const EndpointPrompt: React.FC = ({ title, description, footer }) => ( - -); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts deleted file mode 100644 index dcb2d12f6c986..0000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { AddEmptyPrompt } from './add_empty_prompt'; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx index e1dae72d402f7..c39bc69fc300b 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx @@ -11,7 +11,6 @@ import { EuiPageTemplate } from '@elastic/eui'; import { useQueryInferenceEndpoints } from '../hooks/use_inference_endpoints'; import { TabularPage } from './all_inference_endpoints/tabular_page'; -import { AddEmptyPrompt } from './empty_prompt/add_empty_prompt'; import { InferenceEndpointsHeader } from './inference_endpoints_header'; export const InferenceEndpoints: React.FC = () => { @@ -21,13 +20,9 @@ export const InferenceEndpoints: React.FC = () => { return ( <> - {inferenceEndpoints.length > 0 && } - - {inferenceEndpoints.length === 0 ? ( - - ) : ( - - )} + + + ); diff --git a/x-pack/plugins/search_inference_endpoints/public/locators.ts b/x-pack/plugins/search_inference_endpoints/public/locators.ts new file mode 100644 index 0000000000000..5b32e5648cc7c --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/locators.ts @@ -0,0 +1,29 @@ +/* + * 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 { LocatorDefinition } from '@kbn/share-plugin/common'; +import type { SharePluginSetup } from '@kbn/share-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; + +import { PLUGIN_ID } from '../common/constants'; +import { SEARCH_INFERENCE_ENDPOINTS_PATH } from './routes'; + +export function registerLocators(share: SharePluginSetup) { + share.url.locators.create(new SearchInferenceEndpointLocatorDefinition()); +} + +class SearchInferenceEndpointLocatorDefinition implements LocatorDefinition { + public readonly getLocation = async () => { + return { + app: PLUGIN_ID, + path: SEARCH_INFERENCE_ENDPOINTS_PATH, + state: {}, + }; + }; + + public readonly id = 'SEARCH_INFERENCE_ENDPOINTS'; +} diff --git a/x-pack/plugins/search_inference_endpoints/public/plugin.ts b/x-pack/plugins/search_inference_endpoints/public/plugin.ts index a864b7c0fcdd6..cb60f611b3bb3 100644 --- a/x-pack/plugins/search_inference_endpoints/public/plugin.ts +++ b/x-pack/plugins/search_inference_endpoints/public/plugin.ts @@ -12,16 +12,20 @@ import { Plugin, PluginInitializerContext, } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; import { PLUGIN_ID, PLUGIN_NAME } from '../common/constants'; import { docLinks } from '../common/doc_links'; import { InferenceEndpoints, getInferenceEndpointsProvider } from './embeddable'; import { + AppPluginSetupDependencies, AppPluginStartDependencies, SearchInferenceEndpointsConfigType, SearchInferenceEndpointsPluginSetup, SearchInferenceEndpointsPluginStart, } from './types'; import { INFERENCE_ENDPOINTS_UI_FLAG } from '.'; +import { registerLocators } from './locators'; +import { INFERENCE_ENDPOINTS_PATH } from './components/routes'; export class SearchInferenceEndpointsPlugin implements Plugin @@ -33,7 +37,8 @@ export class SearchInferenceEndpointsPlugin } public setup( - core: CoreSetup + core: CoreSetup, + plugins: AppPluginSetupDependencies ): SearchInferenceEndpointsPluginSetup { if ( !this.config.ui?.enabled && @@ -42,7 +47,16 @@ export class SearchInferenceEndpointsPlugin return {}; core.application.register({ id: PLUGIN_ID, - appRoute: '/app/search_inference_endpoints', + appRoute: '/app/elasticsearch/relevance', + deepLinks: [ + { + id: 'inferenceEndpoints', + path: `/${INFERENCE_ENDPOINTS_PATH}`, + title: i18n.translate('xpack.searchInferenceEndpoints.InferenceEndpointsLinkLabel', { + defaultMessage: 'Inference Endpoints', + }), + }, + ], title: PLUGIN_NAME, async mount({ element, history }: AppMountParameters) { const { renderApp } = await import('./application'); @@ -56,6 +70,8 @@ export class SearchInferenceEndpointsPlugin }, }); + registerLocators(plugins.share); + return {}; } diff --git a/x-pack/plugins/search_inference_endpoints/public/types.ts b/x-pack/plugins/search_inference_endpoints/public/types.ts index 4bd83521cf8d6..9f73d0d0033b0 100644 --- a/x-pack/plugins/search_inference_endpoints/public/types.ts +++ b/x-pack/plugins/search_inference_endpoints/public/types.ts @@ -5,12 +5,14 @@ * 2.0. */ -import type { ConsolePluginStart } from '@kbn/console-plugin/public'; +import type { ConsolePluginSetup, ConsolePluginStart } from '@kbn/console-plugin/public'; import { HttpStart } from '@kbn/core-http-browser'; import { AppMountParameters } from '@kbn/core/public'; import { MlPluginStart } from '@kbn/ml-plugin/public'; -import { SharePluginStart } from '@kbn/share-plugin/public'; +import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import React from 'react'; + +import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { App } from './components/app'; import type { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; @@ -27,12 +29,21 @@ export interface AppPluginStartDependencies { history: AppMountParameters['history']; share: SharePluginStart; console?: ConsolePluginStart; + serverless?: ServerlessPluginStart; +} + +export interface AppPluginSetupDependencies { + history: AppMountParameters['history']; + share: SharePluginSetup; + console?: ConsolePluginSetup; } export interface AppServicesContext { http: HttpStart; ml?: MlPluginStart; console?: ConsolePluginStart; + serverless?: ServerlessPluginStart; + share: SharePluginStart; } export interface InferenceUsageResponse { diff --git a/x-pack/plugins/search_inference_endpoints/tsconfig.json b/x-pack/plugins/search_inference_endpoints/tsconfig.json index d454be99b65f0..9a5b160779e7a 100644 --- a/x-pack/plugins/search_inference_endpoints/tsconfig.json +++ b/x-pack/plugins/search_inference_endpoints/tsconfig.json @@ -33,7 +33,9 @@ "@kbn/features-plugin", "@kbn/ui-theme", "@kbn/deeplinks-search", - "@kbn/deeplinks-management" + "@kbn/deeplinks-management", + "@kbn/serverless", + "@kbn/utility-types" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index ada6529e8d325..77cb502273850 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -36024,15 +36024,6 @@ "xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "Point de terminaison ajouté", "xpack.searchInferenceEndpoints.actions.endpointAddedSuccessDescription": "Le point de terminaison d'inférence \"{endpointId}\" a été ajouté.", "xpack.searchInferenceEndpoints.actions.trainedModelsStatGatherFailed": "Échec de la récupération des statistiques du modèle entraîné", - "xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription": "Les points de terminaison d'inférence vous permettent d'effectuer des tâches d'inférence à l'aide de modèles NLP fournis par des services tiers ou de modèles intégrés d'Elastic comme ELSER et E5. Configurez des tâches telles que l'incorporation de texte, les complétions, le reclassement et bien plus encore à l'aide de la fonction Créer une API d'inférence.", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description": "E5 est un modèle NLP tiers qui vous permet de réaliser des recherches sémantiques multilingues en utilisant des représentations vectorielles denses.", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title": "E5 multilingue", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription": "ELSER est le modèle NLP vectoriel creux d'Elastic pour la recherche sémantique en anglais.", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle": "ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints": "Découvrez comment créer des points de terminaison d'inférence", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5": "Recherche sémantique avec E5 multilingue", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser": "Recherche sémantique avec ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel": "En savoir plus sur les modèles NLP intégrés :", "xpack.searchInferenceEndpoints.allInferenceEndpoints.description": "Les points de terminaison d'inférence rationalisent le déploiement et la gestion des modèles d'apprentissage automatique dans Elasticsearch. Configurez et gérez des tâches NLP à l'aide de points de terminaison uniques afin de créer une recherche basée sur l'IA.", "xpack.searchInferenceEndpoints.apiDocumentationLink": "Documentation sur les API", "xpack.searchInferenceEndpoints.cancel": "Annuler", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 00e756f7bbfd8..c199283dd34af 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -35993,15 +35993,6 @@ "xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "エンドポイントが追加されました", "xpack.searchInferenceEndpoints.actions.endpointAddedSuccessDescription": "インターフェースエンドポイント\"{endpointId}\"が追加されました。", "xpack.searchInferenceEndpoints.actions.trainedModelsStatGatherFailed": "学習済みモデル統計情報を取得できませんでした", - "xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription": "推論エンドポイントを使用すると、サードパーティサービスが提供するNLPモデルや、ELSERやE5などのElasticの組み込みモデルを使用して推論タスクを実行できます。Create Inference APIを使用して、テキスト埋め込み、入力、再ランク付けなどのタスクを設定します。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description": "E5は、密ベクトル表現を使用して、多言語のセマンティック検索を可能にするサードパーティNLPモデルです。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title": "E5 Multilingual", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription": "ELSERは、英語でのセマンティック検索向けにElasticの疎ベクトルNLPモデルです。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle": "ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints": "推論エンドポイントを作成する方法", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5": "E5 Multilingualを使用したセマンティック検索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser": "ELSERを使用したセマンティック検索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel": "組み込まれたNLPモデルの詳細:", "xpack.searchInferenceEndpoints.allInferenceEndpoints.description": "推論エンドポイントは、Elasticsearchにおける機械学習モデルのデプロイと管理を合理化します。独自のエンドポイントを使用してNLPタスクを設定および管理し、AIを活用した検索を構築します。", "xpack.searchInferenceEndpoints.apiDocumentationLink": "APIドキュメント", "xpack.searchInferenceEndpoints.cancel": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3c58620180775..0588fe925cb72 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -35433,15 +35433,6 @@ "xpack.searchInferenceEndpoints.actions.endpointAddedFailure": "终端创建失败", "xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "已添加终端", "xpack.searchInferenceEndpoints.actions.trainedModelsStatGatherFailed": "无法检索已训练模型统计信息", - "xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription": "利用推理终端,您可以使用由第三方服务提供的 NLP 模型或 ELSER 和 E5 等 Elastic 内置模型来执行推理任务。通过使用创建推理 API 来设置任务,如文本嵌入、完成、重新排名等。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description": "E5 是一个第三方 NLP 模型,它允许您通过使用密集向量表示方法来执行多语言语义搜索。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title": "E5 多语言", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription": "ELSER 是 Elastic 的英文版稀疏向量语义搜索 NLP 模型。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle": "ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints": "了解如何创建推理终端", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5": "利用 E5 多语言版进行语义搜索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser": "利用 ELSER 进行语义搜索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel": "详细了解内置 NLP 模型:", "xpack.searchInferenceEndpoints.allInferenceEndpoints.description": "推理终端简化了 Elasticsearch 中的 Machine Learning 模型部署和管理。使用唯一的终端来设置和管理 NLP 任务,以构建 AI 驱动式搜索。", "xpack.searchInferenceEndpoints.apiDocumentationLink": "API 文档", "xpack.searchInferenceEndpoints.cancel": "取消", diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts index 2c894b28b065f..e10e98529b8bf 100644 --- a/x-pack/test_serverless/functional/page_objects/index.ts +++ b/x-pack/test_serverless/functional/page_objects/index.ts @@ -25,6 +25,7 @@ import { SvlSearchIndexDetailPageProvider } from './svl_search_index_detail_page import { SvlSearchElasticsearchStartPageProvider } from './svl_search_elasticsearch_start_page'; import { SvlApiKeysProvider } from './svl_api_keys'; import { SvlSearchCreateIndexPageProvider } from './svl_search_create_index_page'; +import { SvlSearchInferenceManagementPageProvider } from './svl_search_inference_management_page'; export const pageObjects = { ...xpackFunctionalPageObjects, @@ -47,4 +48,5 @@ export const pageObjects = { svlSearchElasticsearchStartPage: SvlSearchElasticsearchStartPageProvider, svlApiKeys: SvlApiKeysProvider, svlSearchCreateIndexPage: SvlSearchCreateIndexPageProvider, + svlSearchInferenceManagementPage: SvlSearchInferenceManagementPageProvider, }; diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_inference_management_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_inference_management_page.ts new file mode 100644 index 0000000000000..4424238a9c809 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/svl_search_inference_management_page.ts @@ -0,0 +1,127 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SvlSearchInferenceManagementPageProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + + return { + InferenceTabularPage: { + async expectHeaderToBeExist() { + await testSubjects.existOrFail('allInferenceEndpointsPage'); + await testSubjects.existOrFail('api-documentation'); + await testSubjects.existOrFail('view-your-models'); + }, + + async expectTabularViewToBeLoaded() { + await testSubjects.existOrFail('search-field-endpoints'); + await testSubjects.existOrFail('type-field-endpoints'); + await testSubjects.existOrFail('service-field-endpoints'); + + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + expect(rows.length).to.equal(2); + + const elserEndpointCell = await rows[0].findByTestSubject('endpointCell'); + const elserEndpointName = await elserEndpointCell.getVisibleText(); + expect(elserEndpointName).to.contain('.elser-2-elasticsearch'); + + const elserProviderCell = await rows[0].findByTestSubject('providerCell'); + const elserProviderName = await elserProviderCell.getVisibleText(); + expect(elserProviderName).to.contain('Elasticsearch'); + expect(elserProviderName).to.contain('.elser_model_2'); + + const elserTypeCell = await rows[0].findByTestSubject('typeCell'); + const elserTypeName = await elserTypeCell.getVisibleText(); + expect(elserTypeName).to.contain('sparse_embedding'); + + const e5EndpointCell = await rows[1].findByTestSubject('endpointCell'); + const e5EndpointName = await e5EndpointCell.getVisibleText(); + expect(e5EndpointName).to.contain('.multilingual-e5-small-elasticsearch'); + + const e5ProviderCell = await rows[1].findByTestSubject('providerCell'); + const e5ProviderName = await e5ProviderCell.getVisibleText(); + expect(e5ProviderName).to.contain('Elasticsearch'); + expect(e5ProviderName).to.contain('.multilingual-e5-small'); + + const e5TypeCell = await rows[1].findByTestSubject('typeCell'); + const e5TypeName = await e5TypeCell.getVisibleText(); + expect(e5TypeName).to.contain('text_embedding'); + }, + + async expectPreconfiguredEndpointsCannotBeDeleted() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const elserDeleteAction = await rows[0].findByTestSubject('inferenceUIDeleteAction'); + const e5DeleteAction = await rows[1].findByTestSubject('inferenceUIDeleteAction'); + + expect(await elserDeleteAction.isEnabled()).to.be(false); + expect(await e5DeleteAction.isEnabled()).to.be(false); + }, + + async expectEndpointWithoutUsageTobeDelete() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const userCreatedEndpoint = await rows[2].findByTestSubject('inferenceUIDeleteAction'); + + await userCreatedEndpoint.click(); + await testSubjects.existOrFail('deleteModalForInferenceUI'); + await testSubjects.existOrFail('deleteModalInferenceEndpointName'); + + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.existOrFail('inferenceEndpointTable'); + }, + + async expectEndpointWithUsageTobeDelete() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const userCreatedEndpoint = await rows[2].findByTestSubject('inferenceUIDeleteAction'); + + await userCreatedEndpoint.click(); + await testSubjects.existOrFail('deleteModalForInferenceUI'); + await testSubjects.existOrFail('deleteModalInferenceEndpointName'); + + const items = await testSubjects.findAll('usageItem'); + expect(items.length).to.equal(2); + + const index = await items[0].getVisibleText(); + const pipeline = await items[1].getVisibleText(); + + expect(index.includes('elser_index')).to.be(true); + expect(pipeline.includes('endpoint-1')).to.be(true); + + expect(await testSubjects.isEnabled('confirmModalConfirmButton')).to.be(false); + + await testSubjects.click('warningCheckbox'); + + expect(await testSubjects.isEnabled('confirmModalConfirmButton')).to.be(true); + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.existOrFail('inferenceEndpointTable'); + }, + + async expectToCopyEndpoint() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const elserCopyEndpointId = await rows[0].findByTestSubject( + 'inference-endpoints-action-copy-id-label' + ); + + await elserCopyEndpointId.click(); + expect((await browser.getClipboardValue()).includes('.elser-2-elasticsearch')).to.be(true); + }, + }, + }; +} diff --git a/x-pack/test_serverless/functional/services/svl_search_navigation.ts b/x-pack/test_serverless/functional/services/svl_search_navigation.ts index 1f27cf18ec8cb..434a5bd4e42ab 100644 --- a/x-pack/test_serverless/functional/services/svl_search_navigation.ts +++ b/x-pack/test_serverless/functional/services/svl_search_navigation.ts @@ -47,5 +47,10 @@ export function SvlSearchNavigationServiceProvider({ }); await testSubjects.existOrFail('searchIndicesDetailsPage', { timeout: 2000 }); }, + async navigateToInferenceManagementPage(expectRedirect: boolean = false) { + await PageObjects.common.navigateToApp('searchInferenceEndpoints', { + shouldLoginIfPrompted: false, + }); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts index 5c52828a11659..47763068455b5 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.ts @@ -45,5 +45,8 @@ export default createTestConfig({ elasticsearchIndices: { pathname: '/app/elasticsearch/indices', }, + searchInferenceEndpoints: { + pathname: '/app/elasticsearch/relevance/inference_endpoints', + }, }, }); diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts index 99190ae0cc072..dd7021aebe800 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.ts @@ -28,5 +28,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./search_playground/playground_overview')); loadTestFile(require.resolve('./ml')); loadTestFile(require.resolve('./custom_role_access')); + loadTestFile(require.resolve('./inference_management')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/inference_management.ts b/x-pack/test_serverless/functional/test_suites/search/inference_management.ts new file mode 100644 index 0000000000000..f820b9bb08085 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/inference_management.ts @@ -0,0 +1,103 @@ +/* + * 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { testHasEmbeddedConsole } from './embedded_console'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects([ + 'svlCommonPage', + 'embeddedConsole', + 'svlSearchInferenceManagementPage', + ]); + const svlSearchNavigation = getService('svlSearchNavigation'); + const browser = getService('browser'); + const ml = getService('ml'); + + describe('Serverless Inference Management UI', function () { + const endpoint = 'endpoint-1'; + const taskType = 'sparse_embedding'; + const modelConfig = { + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + }, + }; + + before(async () => { + await pageObjects.svlCommonPage.loginWithRole('developer'); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + beforeEach(async () => { + await svlSearchNavigation.navigateToInferenceManagementPage(); + }); + + describe('endpoint tabular view', () => { + it('is loaded successfully', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectHeaderToBeExist(); + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectTabularViewToBeLoaded(); + }); + + it('preconfigured endpoints can not be deleted', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectPreconfiguredEndpointsCannotBeDeleted(); + }); + }); + + describe('copy endpoint id action', () => { + it('can copy an endpoint id', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectToCopyEndpoint(); + }); + }); + + describe('delete action', () => { + const usageIndex = 'elser_index'; + beforeEach(async () => { + await ml.api.createInferenceEndpoint(endpoint, taskType, modelConfig); + await browser.refresh(); + }); + + after(async () => { + await ml.api.deleteIndices(usageIndex); + await ml.api.deleteIngestPipeline(endpoint); + }); + + it('deletes modal successfully without any usage', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectEndpointWithoutUsageTobeDelete(); + }); + + it('deletes modal successfully with usage', async () => { + const indexMapping: estypes.MappingTypeMapping = { + properties: { + content: { + type: 'text', + }, + content_embedding: { + type: 'semantic_text', + inference_id: endpoint, + }, + }, + }; + await ml.api.createIngestPipeline(endpoint); + await ml.api.createIndex(usageIndex, indexMapping); + + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectEndpointWithUsageTobeDelete(); + }); + }); + + it('has embedded dev console', async () => { + await testHasEmbeddedConsole(pageObjects); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index 24009866b2b15..74d1c59cbc8e5 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -95,17 +95,17 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); // check Relevance // > Inference Endpoints - // await solutionNavigation.sidenav.clickLink({ - // deepLinkId: 'searchInferenceEndpoints', - // }); - // await solutionNavigation.sidenav.expectLinkActive({ - // deepLinkId: 'searchInferenceEndpoints', - // }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Inference Endpoints' }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ - // deepLinkId: 'searchInferenceEndpoints', - // }); + await solutionNavigation.sidenav.clickLink({ + deepLinkId: 'searchInferenceEndpoints', + }); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'searchInferenceEndpoints', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Inference Endpoints' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'searchInferenceEndpoints', + }); // check Analyze // > Discover @@ -245,8 +245,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await solutionNavigation.sidenav.expectLinkExists({ text: 'Build' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Dev Tools' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Playground' }); - // await solutionNavigation.sidenav.expectLinkExists({ text: 'Relevance' }); - // await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Relevance' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Analyze' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Discover' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Dashboards' }); @@ -270,8 +270,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { 'build', 'dev_tools', 'searchPlayground', - // 'relevance', - // 'searchInferenceEndpoints', + 'relevance', + 'searchInferenceEndpoints', 'analyze', 'discover', 'dashboards',