Skip to content

Commit

Permalink
[8.x] [Search: Inference Management UI] Adding restriction on deletin…
Browse files Browse the repository at this point in the history
…g preconfigured endpoints (#196580) (#197720)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Search: Inference Management UI] Adding restriction on deleting
preconfigured endpoints
(#196580)](#196580)

<!--- Backport version: 8.9.8 -->

### 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-10-23T08:15:35Z","message":"[Search:
Inference Management UI] Adding restriction on deleting preconfigured
endpoints (#196580)\n\n## Summary\r\n\r\nDisables the delete action when
the endpoints are preconfigured.\r\n\r\n![Screenshot 2024-10-18 at 12
12\r\n20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b)\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\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] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[X] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[X] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","v8.16.0","backport:version","v8.17.0"],"number":196580,"url":"https://github.com/elastic/kibana/pull/196580","mergeCommit":{"message":"[Search:
Inference Management UI] Adding restriction on deleting preconfigured
endpoints (#196580)\n\n## Summary\r\n\r\nDisables the delete action when
the endpoints are preconfigured.\r\n\r\n![Screenshot 2024-10-18 at 12
12\r\n20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b)\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\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] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[X] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[X] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196580","number":196580,"mergeCommit":{"message":"[Search:
Inference Management UI] Adding restriction on deleting preconfigured
endpoints (#196580)\n\n## Summary\r\n\r\nDisables the delete action when
the endpoints are preconfigured.\r\n\r\n![Screenshot 2024-10-18 at 12
12\r\n20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b)\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\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] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[X] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[X] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc"}},{"branch":"8.16","label":"v8.16.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/197378","number":197378,"state":"MERGED","mergeCommit":{"sha":"da9808cfc6aed9b2d801316a7e9ca6fcef6474aa","message":"[8.16]
[Search: Inference Management UI] Adding restriction on deleting
preconfigured endpoints (#196580) (#197378)\n\n# Backport\n\nThis will
backport the following commits from `main` to `8.16`:\n- [[Search:
Inference Management UI] Adding restriction on deleting\npreconfigured
endpoints\n(#196580)](https://github.com/elastic/kibana/pull/196580)\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-10-23T08:15:35Z\",\"message\":\"[Search:\nInference
Management UI] Adding restriction on deleting preconfigured\nendpoints
(#196580)\\n\\n## Summary\\r\\n\\r\\nDisables the delete action
when\nthe endpoints are preconfigured.\\r\\n\\r\\n![Screenshot
2024-10-18 at
12\n12\\r\\n20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b)\\r\\n\\r\\n\\r\\n\\r\\n###\nChecklist\\r\\n\\r\\nDelete
any items that are not applicable to this\nPR.\\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] Any UI\ntouched
in this PR does not create any new axe failures\\r\\n(run axe
in\nbrowser:\\r\\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\\r\\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\\r\\n-\n[X]
This renders correctly on smaller devices using
a\nresponsive\\r\\nlayout. (You can test this
[in\nyour\\r\\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\\r\\n-\n[X]
This was checked
for\n[cross-browser\\r\\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\",\"sha\":\"dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc\",\"branchLabelMapping\":{\"^v9.0.0$\":\"main\",\"^v8.17.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:skip\",\"v9.0.0\",\"Team:Search\",\"v8.16.0\",\"backport:version\"],\"title\":\"[Search:\nInference
Management UI] Adding restriction on deleting
preconfigured\nendpoints\",\"number\":196580,\"url\":\"https://github.com/elastic/kibana/pull/196580\",\"mergeCommit\":{\"message\":\"[Search:\nInference
Management UI] Adding restriction on deleting preconfigured\nendpoints
(#196580)\\n\\n## Summary\\r\\n\\r\\nDisables the delete action
when\nthe endpoints are preconfigured.\\r\\n\\r\\n![Screenshot
2024-10-18 at
12\n12\\r\\n20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b)\\r\\n\\r\\n\\r\\n\\r\\n###\nChecklist\\r\\n\\r\\nDelete
any items that are not applicable to this\nPR.\\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] Any UI\ntouched
in this PR does not create any new axe failures\\r\\n(run axe
in\nbrowser:\\r\\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\\r\\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\\r\\n-\n[X]
This renders correctly on smaller devices using
a\nresponsive\\r\\nlayout. (You can test this
[in\nyour\\r\\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\\r\\n-\n[X]
This was checked
for\n[cross-browser\\r\\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\",\"sha\":\"dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"8.16\"],\"targetPullRequestStates\":[{\"branch\":\"main\",\"label\":\"v9.0.0\",\"branchLabelMappingKey\":\"^v9.0.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/196580\",\"number\":196580,\"mergeCommit\":{\"message\":\"[Search:\nInference
Management UI] Adding restriction on deleting preconfigured\nendpoints
(#196580)\\n\\n## Summary\\r\\n\\r\\nDisables the delete action
when\nthe endpoints are preconfigured.\\r\\n\\r\\n![Screenshot
2024-10-18 at
12\n12\\r\\n20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b)\\r\\n\\r\\n\\r\\n\\r\\n###\nChecklist\\r\\n\\r\\nDelete
any items that are not applicable to this\nPR.\\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] Any UI\ntouched
in this PR does not create any new axe failures\\r\\n(run axe
in\nbrowser:\\r\\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\\r\\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\\r\\n-\n[X]
This renders correctly on smaller devices using
a\nresponsive\\r\\nlayout. (You can test this
[in\nyour\\r\\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\\r\\n-\n[X]
This was checked
for\n[cross-browser\\r\\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\",\"sha\":\"dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc\"}},{\"branch\":\"8.16\",\"label\":\"v8.16.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.17.0","labelRegex":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
  • Loading branch information
Samiul-TheSoccerFan authored Oct 24, 2024
1 parent 49239bf commit f7a87e4
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ export const DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE: AllInferenceEndpointsTable
};

export const PIPELINE_URL = 'ingest/ingest_pipelines';

export const PRECONFIGURED_ENDPOINTS = {
ELSER: '.elser-2',
E5: '.multi-e5-small',
};
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const ConfirmDeleteEndpointModal: React.FC<ConfirmDeleteEndpointModalProp
onConfirm={onConfirm}
title={i18n.DELETE_TITLE}
confirmButtonDisabled={deleteDisabled}
data-test-subj="deleteModalForInferenceUI"
>
<EuiFlexGroup gutterSize="l" direction="column">
<EuiFlexItem grow={false}>{i18n.CONFIRM_DELETE_WARNING}</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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, screen, fireEvent } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

import { DeleteAction } from './delete_action';
import { InferenceEndpointUI } from '../../../../types';

describe('Delete Action', () => {
const mockProvider = {
inference_id: 'my-hugging-face',
service: 'hugging_face',
service_settings: {
api_key: 'aaaa',
url: 'https://dummy.huggingface.com',
},
task_settings: {},
} as any;

const mockItem: InferenceEndpointUI = {
endpoint: 'my-hugging-face',
provider: mockProvider,
type: 'text_embedding',
};

const Wrapper = ({ item }: { item: InferenceEndpointUI }) => {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<DeleteAction selectedEndpoint={item} />
</QueryClientProvider>
);
};
it('renders', () => {
render(<Wrapper item={mockItem} />);

expect(screen.getByTestId('inferenceUIDeleteAction')).toBeEnabled();
});

it('disable the delete action for preconfigured endpoint', () => {
const preconfiguredMockItem = { ...mockItem, endpoint: '.elser-2' };
render(<Wrapper item={preconfiguredMockItem} />);

expect(screen.getByTestId('inferenceUIDeleteAction')).toBeDisabled();
});

it('loads confirm delete modal', () => {
render(<Wrapper item={mockItem} />);

fireEvent.click(screen.getByTestId('inferenceUIDeleteAction'));
expect(screen.getByTestId('deleteModalForInferenceUI')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { isEndpointPreconfigured } from '../../../../../../utils/preconfigured_endpoint_helper';
import { useDeleteEndpoint } from '../../../../../../hooks/use_delete_endpoint';
import { InferenceEndpointUI } from '../../../../types';
import { ConfirmDeleteEndpointModal } from './confirm_delete_endpoint';
Expand Down Expand Up @@ -39,6 +40,8 @@ export const DeleteAction: React.FC<DeleteActionProps> = ({ selectedEndpoint })
defaultMessage: 'Delete inference endpoint {selectedEndpointName}',
values: { selectedEndpointName: selectedEndpoint.endpoint },
})}
data-test-subj="inferenceUIDeleteAction"
disabled={isEndpointPreconfigured(selectedEndpoint.endpoint)}
key="delete"
iconType="trash"
color="danger"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@
* 2.0.
*/

import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import { isEndpointPreconfigured } from '../../../../utils/preconfigured_endpoint_helper';
import * as i18n from './translations';

export interface EndpointInfoProps {
inferenceId: string;
}

export const EndpointInfo: React.FC<EndpointInfoProps> = ({ inferenceId }) => (
<span>
<strong>{inferenceId}</strong>
</span>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<span>
<strong>{inferenceId}</strong>
</span>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span>
{isEndpointPreconfigured(inferenceId) ? (
<EuiBetaBadge label={i18n.PRECONFIGURED_LABEL} size="s" color="hollow" />
) : null}
</span>
</EuiFlexItem>
</EuiFlexGroup>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const PRECONFIGURED_LABEL = i18n.translate(
'xpack.searchInferenceEndpoints.elasticsearch.endpointInfo.preconfigured',
{
defaultMessage: 'PRECONFIGURED',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,28 @@ const inferenceEndpoints = [
},
task_settings: {},
},
{
inference_id: '.elser-2',
task_type: 'sparse_embedding',
service: 'elasticsearch',
service_settings: {
num_allocations: 1,
num_threads: 1,
model_id: '.elser_model_2',
},
task_settings: {},
},
{
inference_id: '.multi-e5-small',
task_type: 'text_embedding',
service: 'elasticsearch',
service_settings: {
num_allocations: 1,
num_threads: 1,
model_id: '.multilingual-e5-small',
},
task_settings: {},
},
] as InferenceAPIConfigResponse[];

jest.mock('../../hooks/use_delete_endpoint', () => ({
Expand All @@ -58,22 +80,55 @@ describe('When the tabular page is loaded', () => {
render(<TabularPage inferenceEndpoints={inferenceEndpoints} />);

const rows = screen.getAllByRole('row');
expect(rows[1]).toHaveTextContent('local-model');
expect(rows[2]).toHaveTextContent('my-elser-model-05');
expect(rows[3]).toHaveTextContent('third-party-model');
expect(rows[1]).toHaveTextContent('.elser-2');
expect(rows[2]).toHaveTextContent('.multi-e5-small');
expect(rows[3]).toHaveTextContent('local-model');
expect(rows[4]).toHaveTextContent('my-elser-model-05');
expect(rows[5]).toHaveTextContent('third-party-model');
});

it('should display all service and model ids in the table', () => {
render(<TabularPage inferenceEndpoints={inferenceEndpoints} />);

const rows = screen.getAllByRole('row');
expect(rows[1]).toHaveTextContent('Elasticsearch');
expect(rows[1]).toHaveTextContent('.own_model');
expect(rows[1]).toHaveTextContent('.elser_model_2');

expect(rows[2]).toHaveTextContent('Elasticsearch');
expect(rows[2]).toHaveTextContent('.elser_model_2');
expect(rows[2]).toHaveTextContent('.multilingual-e5-small');

expect(rows[3]).toHaveTextContent('OpenAI');
expect(rows[3]).toHaveTextContent('Elasticsearch');
expect(rows[3]).toHaveTextContent('.own_model');

expect(rows[4]).toHaveTextContent('Elasticsearch');
expect(rows[4]).toHaveTextContent('.elser_model_2');

expect(rows[5]).toHaveTextContent('OpenAI');
expect(rows[5]).toHaveTextContent('.own_model');
});

it('should only disable delete action for preconfigured endpoints', () => {
render(<TabularPage inferenceEndpoints={inferenceEndpoints} />);

const deleteActions = screen.getAllByTestId('inferenceUIDeleteAction');

expect(deleteActions[0]).toBeDisabled();
expect(deleteActions[1]).toBeDisabled();
expect(deleteActions[2]).toBeEnabled();
expect(deleteActions[3]).toBeEnabled();
expect(deleteActions[4]).toBeEnabled();
});

it('should show preconfigured badge only for preconfigured endpoints', () => {
render(<TabularPage inferenceEndpoints={inferenceEndpoints} />);

const preconfigured = 'PRECONFIGURED';

const rows = screen.getAllByRole('row');
expect(rows[1]).toHaveTextContent(preconfigured);
expect(rows[2]).toHaveTextContent(preconfigured);
expect(rows[3]).not.toHaveTextContent(preconfigured);
expect(rows[4]).not.toHaveTextContent(preconfigured);
expect(rows[5]).not.toHaveTextContent(preconfigured);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints })
return null;
},
sortable: true,
truncateText: true,
width: '300px',
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { PRECONFIGURED_ENDPOINTS } from '../components/all_inference_endpoints/constants';
import { isEndpointPreconfigured } from './preconfigured_endpoint_helper';

describe('Preconfigured Endpoint helper', () => {
it('return true for preconfigured elser', () => {
expect(isEndpointPreconfigured(PRECONFIGURED_ENDPOINTS.ELSER)).toEqual(true);
});

it('return true for preconfigured e5', () => {
expect(isEndpointPreconfigured(PRECONFIGURED_ENDPOINTS.E5)).toEqual(true);
});

it('return false for other endpoints', () => {
expect(isEndpointPreconfigured('other-endpoints')).toEqual(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { PRECONFIGURED_ENDPOINTS } from '../components/all_inference_endpoints/constants';

export const isEndpointPreconfigured = (endpoint: string) =>
Object.values(PRECONFIGURED_ENDPOINTS).includes(endpoint);

0 comments on commit f7a87e4

Please sign in to comment.