Skip to content

Commit

Permalink
[Search: Inference Management UI] Adding restriction on deleting prec…
Browse files Browse the repository at this point in the history
…onfigured endpoints (elastic#196580)

## 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 dc219f9)

# Conflicts:
#	x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts
  • Loading branch information
Samiul-TheSoccerFan committed Oct 24, 2024
1 parent 9d54a62 commit 5997e0c
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ export const DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE: AllInferenceEndpointsTable
filterOptions: DEFAULT_FILTER_OPTIONS,
queryParams: DEFAULT_QUERY_PARAMS,
};

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 @@ -72,6 +72,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 5997e0c

Please sign in to comment.