[8.x] [Search][ES3] Enable Inference Management UI in ES3 (#200109) (#201496)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Search][ES3] Enable Inference Management UI in ES3
(#200109)](https://github.com/elastic/kibana/pull/200109)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Samiul
Monir","email":"150824886+Samiul-TheSoccerFan@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-11-22T19:10:45Z","message":"[Search][ES3]
Enable Inference Management UI in ES3 (#200109)\n\n##
Summary\r\n\r\nThis PR:\r\n- Enables Inference Management in ES3\r\n-
Fixes small issues to make sure it works in ES3.\r\n- Added FTR
tests.\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following
conditions. \r\n\r\nReviewers should verify this PR satisfies this list
as well.\r\n\r\n- [X] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [X] This was
checked for breaking HTTP API changes, and any breaking\r\nchanges have
been approved by the breaking-change committee.
The\r\n`release_note:breaking` label should be applied in these
situations.\r\n- [X] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [X] The PR description includes
the appropriate Release Notes section,\r\nand the correct
`release_node:*` label is applied per
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"37a0861d28aa3fe9e1a35db0c37aa463226b870b","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","ci:project-deploy-elasticsearch","backport:version","v8.17.0","v8.18.0"],"title":"[Search][ES3]
Enable Inference Management UI in
ES3","number":200109,"url":"https://github.com/elastic/kibana/pull/200109","mergeCommit":{"message":"[Search][ES3]
Enable Inference Management UI in ES3 (#200109)\n\n##
Summary\r\n\r\nThis PR:\r\n- Enables Inference Management in ES3\r\n-
Fixes small issues to make sure it works in ES3.\r\n- Added FTR
tests.\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following
conditions. \r\n\r\nReviewers should verify this PR satisfies this list
as well.\r\n\r\n- [X] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [X] This was
checked for breaking HTTP API changes, and any breaking\r\nchanges have
been approved by the breaking-change committee.
The\r\n`release_note:breaking` label should be applied in these
situations.\r\n- [X] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [X] The PR description includes
the appropriate Release Notes section,\r\nand the correct
`release_node:*` label is applied per
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"37a0861d28aa3fe9e1a35db0c37aa463226b870b"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/200109","number":200109,"mergeCommit":{"message":"[Search][ES3]
Enable Inference Management UI in ES3 (#200109)\n\n##
Summary\r\n\r\nThis PR:\r\n- Enables Inference Management in ES3\r\n-
Fixes small issues to make sure it works in ES3.\r\n- Added FTR
tests.\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following
conditions. \r\n\r\nReviewers should verify this PR satisfies this list
as well.\r\n\r\n- [X] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [X] This was
checked for breaking HTTP API changes, and any breaking\r\nchanges have
been approved by the breaking-change committee.
The\r\n`release_note:breaking` label should be applied in these
situations.\r\n- [X] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [X] The PR description includes
the appropriate Release Notes section,\r\nand the correct
`release_node:*` label is applied per
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"37a0861d28aa3fe9e1a35db0c37aa463226b870b"}},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/201464","number":201464,"state":"MERGED","mergeCommit":{"sha":"5d8b8eb46a6e91b3edd5804158924994d1cce70f","message":"[8.17]
[Search][ES3] Enable Inference Management UI in ES3 (#200109)
(#201464)\n\n# Backport\n\nThis will backport the following commits from
`main` to `8.17`:\n- [[Search][ES3] Enable Inference Management UI in
ES3\n(#200109)](https://github.com/elastic/kibana/pull/200109)\n\n<!---
Backport version: 9.4.3 -->\n\n### Questions ?\nPlease refer to the
[Backport
tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT
[{\"author\":{\"name\":\"Samiul\nMonir\",\"email\":\"150824886+Samiul-TheSoccerFan@users.noreply.github.com\"},\"sourceCommit\":{\"committedDate\":\"2024-11-22T19:10:45Z\",\"message\":\"[Search][ES3]\nEnable
Inference Management UI in ES3
(#200109)\\n\\n##\nSummary\\r\\n\\r\\nThis PR:\\r\\n- Enables Inference
Management in ES3\\r\\n-\nFixes small issues to make sure it works in
ES3.\\r\\n- Added FTR\ntests.\\r\\n\\r\\n\\r\\n###
Checklist\\r\\n\\r\\nCheck the PR satisfies following\nconditions.
\\r\\n\\r\\nReviewers should verify this PR satisfies this list\nas
well.\\r\\n\\r\\n- [X] [Unit
or\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated
or added to match the most common scenarios\\r\\n- [X] This was\nchecked
for breaking HTTP API changes, and any breaking\\r\\nchanges have\nbeen
approved by the breaking-change
committee.\nThe\\r\\n`release_note:breaking` label should be applied in
these\nsituations.\\r\\n- [X]
[Flaky\nTest\\r\\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)\nwas\\r\\nused
on any tests changed\\r\\n- [X] The PR description includes\nthe
appropriate Release Notes section,\\r\\nand the
correct\n`release_node:*` label is applied
per\nthe\\r\\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by:\nkibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\",\"sha\":\"37a0861d28aa3fe9e1a35db0c37aa463226b870b\",\"branchLabelMapping\":{\"^v9.0.0$\":\"main\",\"^v8.18.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:skip\",\"v9.0.0\",\"Team:Search\",\"ci:project-deploy-elasticsearch\",\"backport:version\",\"v8.17.0\"],\"title\":\"[Search][ES3]\nEnable
Inference Management UI
in\nES3\",\"number\":200109,\"url\":\"https://github.com/elastic/kibana/pull/200109\",\"mergeCommit\":{\"message\":\"[Search][ES3]\nEnable
Inference Management UI in ES3
(#200109)\\n\\n##\nSummary\\r\\n\\r\\nThis PR:\\r\\n- Enables Inference
Management in ES3\\r\\n-\nFixes small issues to make sure it works in
ES3.\\r\\n- Added FTR\ntests.\\r\\n\\r\\n\\r\\n###
Checklist\\r\\n\\r\\nCheck the PR satisfies following\nconditions.
\\r\\n\\r\\nReviewers should verify this PR satisfies this list\nas
well.\\r\\n\\r\\n- [X] [Unit
or\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated
or added to match the most common scenarios\\r\\n- [X] This was\nchecked
for breaking HTTP API changes, and any breaking\\r\\nchanges have\nbeen
approved by the breaking-change
committee.\nThe\\r\\n`release_note:breaking` label should be applied in
these\nsituations.\\r\\n- [X]
[Flaky\nTest\\r\\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)\nwas\\r\\nused
on any tests changed\\r\\n- [X] The PR description includes\nthe
appropriate Release Notes section,\\r\\nand the
correct\n`release_node:*` label is applied
per\nthe\\r\\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by:\nkibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\",\"sha\":\"37a0861d28aa3fe9e1a35db0c37aa463226b870b\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"8.17\"],\"targetPullRequestStates\":[{\"branch\":\"main\",\"label\":\"v9.0.0\",\"branchLabelMappingKey\":\"^v9.0.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/200109\",\"number\":200109,\"mergeCommit\":{\"message\":\"[Search][ES3]\nEnable
Inference Management UI in ES3
(#200109)\\n\\n##\nSummary\\r\\n\\r\\nThis PR:\\r\\n- Enables Inference
Management in ES3\\r\\n-\nFixes small issues to make sure it works in
ES3.\\r\\n- Added FTR\ntests.\\r\\n\\r\\n\\r\\n###
Checklist\\r\\n\\r\\nCheck the PR satisfies following\nconditions.
\\r\\n\\r\\nReviewers should verify this PR satisfies this list\nas
well.\\r\\n\\r\\n- [X] [Unit
or\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated
or added to match the most common scenarios\\r\\n- [X] This was\nchecked
for breaking HTTP API changes, and any breaking\\r\\nchanges have\nbeen
approved by the breaking-change
committee.\nThe\\r\\n`release_note:breaking` label should be applied in
these\nsituations.\\r\\n- [X]
[Flaky\nTest\\r\\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)\nwas\\r\\nused
on any tests changed\\r\\n- [X] The PR description includes\nthe
appropriate Release Notes section,\\r\\nand the
correct\n`release_node:*` label is applied
per\nthe\\r\\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by:\nkibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\",\"sha\":\"37a0861d28aa3fe9e1a35db0c37aa463226b870b\"}},{\"branch\":\"8.17\",\"label\":\"v8.17.0\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"}]}]\nBACKPORT-->\n\nCo-authored-by:
Samiul Monir
<150824886+Samiul-TheSoccerFan@users.noreply.github.com>"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-11-23 22:38:10 +11:00 committed by GitHub
parent baa453893d
commit eae4dad4a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 609 additions and 393 deletions

View file

@ -81,7 +81,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

View file

@ -1005,5 +1005,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`,
},
});
};

View file

@ -679,6 +679,9 @@ export interface DocLinks {
readonly context: string;
readonly hiddenFields: string;
};
readonly inferenceManagement: {
readonly inferenceAPIDocumentation: string;
};
}
export type BuildFlavor = 'serverless' | 'traditional';

View file

@ -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/',
};

View file

@ -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<CreateIndexLocatorParams>(new CreateIndexLocatorDefinition());
share.url.locators.create<PlaygroundLocatorParams>(new PlaygroundLocatorDefinition());
share.url.locators.create<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition());
}

View file

@ -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<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition());
}
export class SearchInferenceEndpointLocatorDefinition
implements LocatorDefinition<SerializableRecord>
{
public readonly getLocation = async () => {
return {
app: SEARCH_RELEVANCE_PLUGIN.ID,
path: '/inference_endpoints',
state: {},
};
};
public readonly id = 'SEARCH_INFERENCE_ENDPOINTS';
}

View file

@ -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',

View file

@ -727,6 +727,11 @@ describe('<IndexDetailsPage />', () => {
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('<IndexDetailsPage />', () => {
docLinks: {
links: {
ml: '',
enterpriseSearch: '',
inferenceManagement: {
inferenceAPIDocumentation: 'https://abc.com/inference-api-create',
},
},
},
core: {
@ -819,6 +826,20 @@ describe('<IndexDetailsPage />', () => {
},
},
},
share: {
url: {
locators: {
get: jest.fn((id) => {
switch (id) {
case INFERENCE_LOCATOR:
return mockInferenceManagementLocator;
default:
throw new Error(`Unknown locator id: ${id}`);
}
}),
},
},
},
},
},
});

View file

@ -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}`);
}
}),
},
},
},
},
}),
}));

View file

@ -80,13 +80,15 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({
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<boolean>(false);
const [availableTrainedModels, setAvailableTrainedModels] = useState<
@ -224,24 +226,28 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({
panelPaddingSize="m"
closePopover={() => setIsInferencePopoverVisible(!isInferencePopoverVisible)}
>
<EuiContextMenuPanel>
<EuiContextMenuItem
key="manageInferenceEndpointButton"
icon="gear"
size="s"
data-test-subj="manageInferenceEndpointButton"
onClick={async () => {
application.navigateToUrl(inferenceEndpointsPageLink);
}}
>
{i18n.translate(
'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.manageInferenceEndpointButton',
{
defaultMessage: 'Manage Inference Endpoints',
}
)}
</EuiContextMenuItem>
</EuiContextMenuPanel>
{inferenceEndpointsPageLink && (
<EuiContextMenuPanel>
<EuiContextMenuItem
key="manageInferenceEndpointButton"
icon="gear"
size="s"
data-test-subj="manageInferenceEndpointButton"
href={inferenceEndpointsPageLink}
onClick={(e) => {
e.preventDefault();
application.navigateToUrl(inferenceEndpointsPageLink);
}}
>
{i18n.translate(
'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.manageInferenceEndpointButton',
{
defaultMessage: 'Manage Inference Endpoints',
}
)}
</EuiContextMenuItem>
</EuiContextMenuPanel>
)}
<EuiHorizontalRule margin="none" />
<EuiPanel color="transparent" paddingSize="s">
<EuiTitle size="xxxs">
@ -292,7 +298,7 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({
<EuiHorizontalRule margin="none" />
<EuiContextMenuItem icon={<EuiIcon type="help" color="primary" />} size="s">
<EuiLink
href={docLinks.links.enterpriseSearch.inferenceApiCreate}
href={docLinks.links.inferenceManagement.inferenceAPIDocumentation}
target="_blank"
data-test-subj="learn-how-to-create-inference-endpoints"
>

View file

@ -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;
}

View file

@ -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',
});

View file

@ -24,6 +24,7 @@
"optionalPlugins": [
"cloud",
"console",
"serverless"
],
"requiredBundles": [
"kibanaReact"

View file

@ -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';

View file

@ -33,6 +33,7 @@ interface UseFilterParams {
options: MultiSelectFilterOption[];
renderOption?: (option: MultiSelectFilterOption) => React.ReactNode;
selectedOptionKeys?: string[];
dataTestSubj?: string;
}
export const MultiSelectFilter: React.FC<UseFilterParams> = ({
@ -41,6 +42,7 @@ export const MultiSelectFilter: React.FC<UseFilterParams> = ({
options: rawOptions,
selectedOptionKeys = [],
renderOption,
dataTestSubj,
}) => {
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@ -55,7 +57,7 @@ export const MultiSelectFilter: React.FC<UseFilterParams> = ({
);
return (
<EuiFilterGroup>
<EuiFilterGroup data-test-subj={dataTestSubj}>
<EuiPopover
ownFocus
button={

View file

@ -38,6 +38,7 @@ export const ServiceProviderFilter: React.FC<Props> = ({ optionKeys, onChange })
options={options}
renderOption={(option) => option.label}
selectedOptionKeys={optionKeys}
dataTestSubj="service-field-endpoints"
/>
);
};

View file

@ -38,6 +38,7 @@ export const TaskTypeFilter: React.FC<Props> = ({ optionKeys, onChange }) => {
options={options}
renderOption={(option) => option.label}
selectedOptionKeys={optionKeys}
dataTestSubj="type-field-endpoints"
/>
);
};

View file

@ -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(<IndexItem usageItem={item} />);
});
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',
});
});
});

View file

@ -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<UsageProps> = ({ usageItem }) => {
export const IndexItem: React.FC<UsageProps> = ({ usageItem }) => {
const {
services: { application },
services: { application, serverless },
} = useKibana();
const handleNavigateToIndex = () => {
if (usageItem.type === 'Index') {
const navigateToIndex = useCallback(() => {
if (serverless) {
application?.navigateToApp(SEARCH_INDICES, {
path: `${SERVERLESS_INDEX_MANAGEMENT_URL}/${usageItem.id}/data`,
openInNewTab: true,
});
} else {
application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, {
path: `search_indices/${usageItem.id}`,
openInNewTab: true,
});
} else if (usageItem.type === 'Pipeline') {
application?.navigateToApp(MANAGEMENT_APP_ID, {
path: `${PIPELINE_URL}?pipeline=${usageItem.id}`,
openInNewTab: true,
});
}
};
}, [application, serverless, usageItem.id]);
return (
<EuiFlexGroup gutterSize="s" direction="column" data-test-subj="usageItem">
@ -62,7 +63,7 @@ export const UsageItem: React.FC<UsageProps> = ({ usageItem }) => {
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink data-test-subj="navigateToIndexPage" onClick={handleNavigateToIndex}>
<EuiLink data-test-subj="navigateToIndexPage" onClick={navigateToIndex}>
<EuiIcon size="s" type="popout" />
</EuiLink>
</EuiFlexItem>

View file

@ -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<ListUsageResultsProps> = ({ list }) => {
<EuiFlexItem>
{list
.filter((item) => item.id.toLowerCase().includes(term.toLowerCase()))
.map((item, id) => (
<UsageItem usageItem={item} key={id} />
))}
.map((item, id) => {
if (item.type === 'Pipeline') {
return <PipelineItem usageItem={item} key={id} />;
} else {
return <IndexItem usageItem={item} key={id} />;
}
})}
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -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(<UsageItem usageItem={item} />);
});
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(<PipelineItem usageItem={item} />);
});
describe('pipeline', () => {
const item: InferenceUsageInfo = {
id: 'pipeline-1',
type: 'Pipeline',
};
beforeEach(() => {
render(<UsageItem usageItem={item} />);
});
it('renders', () => {
expect(screen.getByText('pipeline-1')).toBeInTheDocument();
expect(screen.getByText('Pipeline')).toBeInTheDocument();

View file

@ -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<UsageProps> = ({ 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 (
<EuiFlexGroup gutterSize="s" direction="column" data-test-subj="usageItem">
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween">
<EuiFlexItem>
<EuiText size="s">
<EuiTextTruncate text={usageItem.id} />
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color="hollow">{usageItem.type}</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink data-test-subj="navigateToPipelinePage" onClick={navigateToPipeline}>
<EuiIcon size="s" type="popout" />
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiHorizontalRule margin="none" />
<EuiSpacer size="s" />
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -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<ScanUsageResultsProps> = ({
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 (

View file

@ -86,6 +86,7 @@ export const ConfirmDeleteEndpointModal: React.FC<ConfirmDeleteEndpointModalProp
font-family: ${euiThemeVars.euiCodeFontFamily};
font-weight: ${euiThemeVars.euiCodeFontWeightBold};
`}
data-test-subj="deleteModalInferenceEndpointName"
>
{inferenceEndpoint.endpoint}
</EuiText>

View file

@ -52,6 +52,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints })
{
field: 'endpoint',
name: i18n.ENDPOINT,
'data-test-subj': 'endpointCell',
render: (endpoint: string) => {
if (endpoint) {
return <EndpointInfo inferenceId={endpoint} />;
@ -65,6 +66,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints })
{
field: 'provider',
name: i18n.SERVICE_PROVIDER,
'data-test-subj': 'providerCell',
render: (provider: InferenceAPIConfigResponse) => {
if (provider) {
return <ServiceProvider providerEndpoint={provider} />;
@ -78,6 +80,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints })
{
field: 'type',
name: i18n.TASK_TYPE,
'data-test-subj': 'typeCell',
render: (type: TaskTypes) => {
if (type) {
return <TaskType type={type} />;
@ -149,6 +152,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints })
onChange={handleTableChange}
pagination={pagination}
sorting={sorting}
data-test-subj="inferenceEndpointTable"
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -1,3 +0,0 @@
.addEmptyPrompt {
max-width: 860px;
}

View file

@ -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(<AddEmptyPrompt />);
});
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();
});
});

View file

@ -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 (
<EuiPageTemplate.EmptyPrompt
layout="horizontal"
restrictWidth
color="plain"
hasShadow
icon={<EuiImage size="fullWidth" src={inferenceEndpoint} alt="" />}
title={<h2>{i18n.INFERENCE_ENDPOINT_LABEL}</h2>}
body={
<EuiFlexGroup direction="column">
<EuiFlexItem data-test-subj="createFirstInferenceEndpointDescription">
{i18n.CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION}
</EuiFlexItem>
<EuiFlexItem>
<EuiLink
href={docLinks.createInferenceEndpoint}
target="_blank"
data-test-subj="learn-how-to-create-inference-endpoints"
>
{i18n.LEARN_HOW_TO_CREATE_INFERENCE_ENDPOINTS_LINK}
</EuiLink>
</EuiFlexItem>
<EuiFlexItem>
<EuiLink href={trainedModelPageUrl} target="_blank" data-test-subj="view-your-models">
{i18n.VIEW_YOUR_MODELS_LINK}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
}
footer={
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem>
<strong>{i18n.START_WITH_PREPARED_ENDPOINTS_LABEL}</strong>
</EuiFlexItem>
<EuiSpacer size="s" />
<EuiFlexGroup>
<EuiFlexItem>
<EndpointPrompt
title={i18n.ELSER_TITLE}
description={i18n.ELSER_DESCRIPTION}
footer={
<EuiLink
href={docLinks.semanticSearchElser}
target="_blank"
data-test-subj="semantic-search-with-elser"
>
{i18n.SEMANTIC_SEARCH_WITH_ELSER_LINK}
</EuiLink>
}
/>
</EuiFlexItem>
<EuiFlexItem>
<EndpointPrompt
title={i18n.E5_TITLE}
description={i18n.E5_DESCRIPTION}
footer={
<EuiLink
href={docLinks.semanticSearchE5}
target="_blank"
data-test-subj="semantic-search-with-e5"
>
{i18n.SEMANTIC_SEARCH_WITH_E5_LINK}
</EuiLink>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
}
/>
);
};

View file

@ -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<EndpointPromptProps> = ({ title, description, footer }) => (
<EuiCard
display="plain"
textAlign="left"
data-test-subj="multilingualE5PromptForEmptyState"
title={title}
titleSize="xs"
description={description}
footer={footer}
/>
);

View file

@ -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';

View file

@ -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 && <InferenceEndpointsHeader />}
<EuiPageTemplate.Section className="eui-yScroll">
{inferenceEndpoints.length === 0 ? (
<AddEmptyPrompt />
) : (
<TabularPage inferenceEndpoints={inferenceEndpoints} />
)}
<InferenceEndpointsHeader />
<EuiPageTemplate.Section className="eui-yScroll" data-test-subj="inferenceManagementPage">
<TabularPage inferenceEndpoints={inferenceEndpoints} />
</EuiPageTemplate.Section>
</>
);

View file

@ -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<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition());
}
class SearchInferenceEndpointLocatorDefinition implements LocatorDefinition<SerializableRecord> {
public readonly getLocation = async () => {
return {
app: PLUGIN_ID,
path: SEARCH_INFERENCE_ENDPOINTS_PATH,
state: {},
};
};
public readonly id = 'SEARCH_INFERENCE_ENDPOINTS';
}

View file

@ -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<SearchInferenceEndpointsPluginSetup, SearchInferenceEndpointsPluginStart>
@ -33,7 +37,8 @@ export class SearchInferenceEndpointsPlugin
}
public setup(
core: CoreSetup<AppPluginStartDependencies, SearchInferenceEndpointsPluginStart>
core: CoreSetup<AppPluginStartDependencies, SearchInferenceEndpointsPluginStart>,
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 {};
}

View file

@ -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 {

View file

@ -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/**/*",

View file

@ -36060,15 +36060,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",

View file

@ -36029,15 +36029,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": "キャンセル",

View file

@ -36094,15 +36094,6 @@
"xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "已添加终端",
"xpack.searchInferenceEndpoints.actions.endpointAddedSuccessDescription": "已添加推理终端“{endpointId}”。",
"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": "取消",

View file

@ -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,
};

View file

@ -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);
},
},
};
}

View file

@ -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,
});
},
};
}

View file

@ -46,5 +46,8 @@ export default createTestConfig({
elasticsearchIndices: {
pathname: '/app/elasticsearch/indices',
},
searchInferenceEndpoints: {
pathname: '/app/elasticsearch/relevance/inference_endpoints',
},
},
});

View file

@ -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'));
});
}

View file

@ -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);
});
});
}

View file

@ -100,17 +100,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
@ -264,8 +264,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' });
@ -288,8 +288,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
'build',
'dev_tools',
'searchPlayground',
// 'relevance',
// 'searchInferenceEndpoints',
'relevance',
'searchInferenceEndpoints',
'analyze',
'discover',
'dashboards',