From 2ba14da20100da0a2a400fd583336ba71c707125 Mon Sep 17 00:00:00 2001 From: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com> Date: Wed, 23 Oct 2024 04:15:35 -0400 Subject: [PATCH] [Search: Inference Management UI] Adding restriction on deleting preconfigured endpoints (#196580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Disables the delete action when the endpoints are preconfigured. ![Screenshot 2024-10-18 at 12 12 20 PM](https://github.com/user-attachments/assets/6684b5c6-5f7d-434f-83e3-74872125753b) ### Checklist Delete any items that are not applicable to this PR. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [X] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [X] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [X] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) (cherry picked from commit dc219f9b309fe1582ce96d2c9ac9d42d91ea56bc) --- .../all_inference_endpoints/constants.ts | 5 ++ .../delete/confirm_delete_endpoint/index.tsx | 1 + .../actions/delete/delete_action.test.tsx | 59 ++++++++++++++++ .../actions/delete/delete_action.tsx | 3 + .../render_endpoint/endpoint_info.tsx | 20 +++++- .../render_endpoint/translations.ts | 15 +++++ .../tabular_page.test.tsx | 67 +++++++++++++++++-- .../all_inference_endpoints/tabular_page.tsx | 1 - .../preconfigured_endpoint_helper.test.ts | 23 +++++++ .../utils/preconfigured_endpoint_helper.ts | 11 +++ 10 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.test.tsx create mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/translations.ts create mode 100644 x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts create mode 100644 x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts 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..26b6a93cec9fa 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,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', +}; 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 ea5c35178f9bf..06bd585c0eb28 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 @@ -75,6 +75,7 @@ export const ConfirmDeleteEndpointModal: React.FC {i18n.CONFIRM_DELETE_WARNING} diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.test.tsx new file mode 100644 index 0000000000000..22c509ca22989 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.test.tsx @@ -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 ( + + + + ); + }; + it('renders', () => { + render(); + + expect(screen.getByTestId('inferenceUIDeleteAction')).toBeEnabled(); + }); + + it('disable the delete action for preconfigured endpoint', () => { + const preconfiguredMockItem = { ...mockItem, endpoint: '.elser-2' }; + render(); + + expect(screen.getByTestId('inferenceUIDeleteAction')).toBeDisabled(); + }); + + it('loads confirm delete modal', () => { + render(); + + fireEvent.click(screen.getByTestId('inferenceUIDeleteAction')); + expect(screen.getByTestId('deleteModalForInferenceUI')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.tsx index f932a3d25ed21..fb31aeb31dcaa 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.tsx @@ -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'; @@ -39,6 +40,8 @@ export const DeleteAction: React.FC = ({ selectedEndpoint }) defaultMessage: 'Delete inference endpoint {selectedEndpointName}', values: { selectedEndpointName: selectedEndpoint.endpoint }, })} + data-test-subj="inferenceUIDeleteAction" + disabled={isEndpointPreconfigured(selectedEndpoint.endpoint)} key="delete" iconType="trash" color="danger" diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/endpoint_info.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/endpoint_info.tsx index 3d810a24a9ffc..26b12328dafd4 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/endpoint_info.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/endpoint_info.tsx @@ -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 = ({ inferenceId }) => ( - - {inferenceId} - + + + + {inferenceId} + + + + + {isEndpointPreconfigured(inferenceId) ? ( + + ) : null} + + + ); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/translations.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/translations.ts new file mode 100644 index 0000000000000..70b1576a9ddc0 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_endpoint/translations.ts @@ -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', + } +); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx index cb23ba650e5fa..91cc303ed4568 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx @@ -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', () => ({ @@ -58,9 +80,11 @@ describe('When the tabular page is loaded', () => { render(); 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', () => { @@ -68,12 +92,43 @@ describe('When the tabular page is loaded', () => { 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(); + + 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(); + + 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); }); }); 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 88a18a25b5a79..4cf4b3112396d 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 @@ -60,7 +60,6 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) return null; }, sortable: true, - truncateText: true, width: '300px', }, { diff --git a/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts new file mode 100644 index 0000000000000..b853109352dd9 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts @@ -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); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts new file mode 100644 index 0000000000000..418e7e95319ef --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts @@ -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);