From d111b63482fa51038a37f1a2390d862c63de66a9 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:16:33 +0800 Subject: [PATCH] [Inference] Fix table responsiveness (#189265) ## Summary This improves the inference endpoint's table responsiveness and code clarity. It also fixes a bug where deleting one endpoint and then a second one would try to delete the same endpoint twice. Screenshot 2024-07-26 at 12 18 59 Screenshot 2024-07-26 at 12 18 52 Screenshot 2024-07-26 at 12 18 37 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit a9ae3e53f12576b6a360fcb714e94c89eaf3751b) --- .../common/constants.ts | 1 + .../common/translations.ts | 21 --- ...ction.test.tsx => copy_id_action.test.tsx} | 12 +- .../actions/copy_id/copy_id_action.tsx | 47 +++++++ .../actions/copy_id/use_copy_id_action.tsx | 44 ------ .../confirm_delete_endpoint/index.test.tsx | 2 +- .../confirm_delete_endpoint/translations.ts | 7 + .../actions/delete/delete_action.tsx | 55 ++++++++ .../actions/delete/use_delete_action.tsx | 55 -------- .../render_actions/actions/types.ts | 12 -- .../render_actions/use_actions.tsx | 79 ----------- .../deployment_status.tsx | 2 +- .../render_endpoint/endpoint_info.tsx | 36 ++--- .../render_table_columns/table_columns.tsx | 80 ----------- .../tabular_page.test.tsx | 11 +- .../all_inference_endpoints/tabular_page.tsx | 130 +++++++++++------- .../public/components/inference_endpoints.tsx | 4 +- .../public/hooks/use_delete_endpoint.tsx | 5 +- .../public/hooks/use_inference_endpoints.ts | 9 +- .../public/hooks/use_table_data.test.tsx | 48 ++++--- .../public/hooks/use_table_data.tsx | 18 ++- .../public/hooks/use_trained_model_stats.ts | 24 ++++ .../search_inference_endpoints/tsconfig.json | 7 +- 23 files changed, 304 insertions(+), 405 deletions(-) rename x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/{use_copy_id_action.test.tsx => copy_id_action.test.tsx} (80%) create mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.tsx delete mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.tsx create mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.tsx delete mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/use_delete_action.tsx delete mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/types.ts delete mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/use_actions.tsx delete mode 100644 x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/table_columns.tsx create mode 100644 x-pack/plugins/search_inference_endpoints/public/hooks/use_trained_model_stats.ts diff --git a/x-pack/plugins/search_inference_endpoints/common/constants.ts b/x-pack/plugins/search_inference_endpoints/common/constants.ts index cdba227626a55b..186901659dd295 100644 --- a/x-pack/plugins/search_inference_endpoints/common/constants.ts +++ b/x-pack/plugins/search_inference_endpoints/common/constants.ts @@ -9,3 +9,4 @@ export const PLUGIN_ID = 'searchInferenceEndpoints'; export const PLUGIN_NAME = 'InferenceEndpoints'; export const INFERENCE_ENDPOINTS_QUERY_KEY = 'inferenceEndpointsQueryKey'; +export const TRAINED_MODEL_STATS_QUERY_KEY = 'trainedModelStats'; diff --git a/x-pack/plugins/search_inference_endpoints/common/translations.ts b/x-pack/plugins/search_inference_endpoints/common/translations.ts index bb04ec80fafdcf..a61d185c406c0a 100644 --- a/x-pack/plugins/search_inference_endpoints/common/translations.ts +++ b/x-pack/plugins/search_inference_endpoints/common/translations.ts @@ -119,20 +119,6 @@ export const FORBIDDEN_TO_ACCESS_TRAINED_MODELS = i18n.translate( } ); -export const COPY_ID_ACTION_LABEL = i18n.translate( - 'xpack.searchInferenceEndpoints.actions.copyID', - { - defaultMessage: 'Copy endpoint ID', - } -); - -export const COPY_ID_ACTION_SUCCESS = i18n.translate( - 'xpack.searchInferenceEndpoints.actions.copyIDSuccess', - { - defaultMessage: 'Inference endpoint ID copied!', - } -); - export const ENDPOINT_ADDED_SUCCESS = i18n.translate( 'xpack.searchInferenceEndpoints.actions.endpointAddedSuccess', { @@ -153,13 +139,6 @@ export const ENDPOINT_ADDED_SUCCESS_DESCRIPTION = (endpointId: string) => values: { endpointId }, }); -export const DELETE_ACTION_LABEL = i18n.translate( - 'xpack.searchInferenceEndpoints.actions.deleteSingleEndpoint', - { - defaultMessage: 'Delete endpoint', - } -); - export const ENDPOINT = i18n.translate('xpack.searchInferenceEndpoints.endpoint', { defaultMessage: 'Endpoint', }); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.test.tsx similarity index 80% rename from x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.test.tsx rename to x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.test.tsx index 1445e0c41c5745..288ba5089d3673 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.test.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.test.tsx @@ -8,7 +8,7 @@ import { renderReactTestingLibraryWithI18n as render } from '@kbn/test-jest-helpers'; import React from 'react'; import { useKibana } from '../../../../../../hooks/use_kibana'; -import { useCopyIDAction } from './use_copy_id_action'; +import { CopyIDAction } from './copy_id_action'; const mockInferenceEndpoint = { deployment: 'not_applicable', @@ -35,8 +35,6 @@ Object.defineProperty(navigator, 'clipboard', { configurable: true, }); -const mockOnActionSuccess = jest.fn(); - jest.mock('../../../../../../hooks/use_kibana', () => ({ useKibana: jest.fn(), })); @@ -53,21 +51,19 @@ const addSuccess = jest.fn(); }, })); -describe('useCopyIDAction hook', () => { +describe('CopyIDAction', () => { beforeEach(() => { jest.clearAllMocks(); }); it('renders the label with correct text', () => { const TestComponent = () => { - const { getAction } = useCopyIDAction({ onActionSuccess: mockOnActionSuccess }); - const action = getAction(mockInferenceEndpoint); - return
{action}
; + return ; }; const { getByTestId } = render(); const labelElement = getByTestId('inference-endpoints-action-copy-id-label'); - expect(labelElement).toHaveTextContent('Copy endpoint ID'); + expect(labelElement).toBeVisible(); }); }); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.tsx new file mode 100644 index 00000000000000..4641899b59720c --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/copy_id_action.tsx @@ -0,0 +1,47 @@ +/* + * 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 { EuiButtonIcon, EuiCopy } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useKibana } from '../../../../../../hooks/use_kibana'; + +interface CopyIDActionProps { + modelId: string; +} + +export const CopyIDAction = ({ modelId }: CopyIDActionProps) => { + const { + services: { notifications }, + } = useKibana(); + const toasts = notifications?.toasts; + + return ( + + {(copy) => ( + { + copy(); + toasts?.addSuccess({ + title: i18n.translate('xpack.searchInferenceEndpoints.actions.copyIDSuccess', { + defaultMessage: 'Inference endpoint ID {modelId} copied', + values: { modelId }, + }), + }); + }} + size="s" + /> + )} + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.tsx deleted file mode 100644 index b43b308af068a0..00000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/copy_id/use_copy_id_action.tsx +++ /dev/null @@ -1,44 +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 { EuiContextMenuItem, EuiCopy, EuiIcon } from '@elastic/eui'; -import React from 'react'; -import * as i18n from '../../../../../../../common/translations'; -import { useKibana } from '../../../../../../hooks/use_kibana'; -import { InferenceEndpointUI } from '../../../../types'; -import { UseCopyIDActionProps } from '../types'; - -export const useCopyIDAction = ({ onActionSuccess }: UseCopyIDActionProps) => { - const { - services: { notifications }, - } = useKibana(); - const toasts = notifications?.toasts; - - const getAction = (inferenceEndpoint: InferenceEndpointUI) => { - return ( - - {(copy) => ( - } - onClick={() => { - copy(); - onActionSuccess(); - toasts?.addSuccess({ title: i18n.COPY_ID_ACTION_SUCCESS }); - }} - size="s" - > - {i18n.COPY_ID_ACTION_LABEL} - - )} - - ); - }; - - return { getAction }; -}; 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.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.test.tsx index e6f4594e2d0cde..8fe5c0e94b0584 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.test.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.test.tsx @@ -18,7 +18,7 @@ describe('ConfirmDeleteEndpointModal', () => { render(); }); - it('renders the modal with correct texts', () => { + it('renders the modal with correct elements', () => { expect(screen.getByText(i18n.DELETE_TITLE)).toBeInTheDocument(); expect(screen.getByText(i18n.CONFIRM_DELETE_WARNING)).toBeInTheDocument(); expect(screen.getByText(i18n.CANCEL)).toBeInTheDocument(); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/translations.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/translations.ts index 6b2dff7d8b8f1d..4e306afcc3ac84 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/translations.ts +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/translations.ts @@ -22,3 +22,10 @@ export const CONFIRM_DELETE_WARNING = i18n.translate( 'Deleting an active endpoint will cause operations targeting associated semantic_text fields and inference pipelines to fail.', } ); + +export const DELETE_ACTION_LABEL = i18n.translate( + 'xpack.searchInferenceEndpoints.actions.deleteSingleEndpoint', + { + defaultMessage: 'Delete endpoint', + } +); 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 new file mode 100644 index 00000000000000..88a982fd0b76e0 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/delete_action.tsx @@ -0,0 +1,55 @@ +/* + * 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 { EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { useDeleteEndpoint } from '../../../../../../hooks/use_delete_endpoint'; +import { InferenceEndpointUI } from '../../../../types'; +import { ConfirmDeleteEndpointModal } from './confirm_delete_endpoint'; + +interface DeleteActionProps { + selectedEndpoint?: InferenceEndpointUI; +} + +export const DeleteAction: React.FC = ({ selectedEndpoint }) => { + const [isModalVisible, setIsModalVisible] = useState(false); + + const { mutate: deleteEndpoint } = useDeleteEndpoint(() => setIsModalVisible(false)); + + const onConfirmDeletion = () => { + if (!selectedEndpoint) { + return; + } + + deleteEndpoint({ + type: selectedEndpoint.type, + id: selectedEndpoint.endpoint.model_id, + }); + }; + + return ( + <> + setIsModalVisible(true)} + /> + {isModalVisible && ( + setIsModalVisible(false)} + onConfirm={onConfirmDeletion} + /> + )} + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/use_delete_action.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/use_delete_action.tsx deleted file mode 100644 index 1a2e5cdb5b51f4..00000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/use_delete_action.tsx +++ /dev/null @@ -1,55 +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 { EuiContextMenuItem, EuiIcon } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; -import * as i18n from '../../../../../../../common/translations'; -import { useDeleteEndpoint } from '../../../../../../hooks/use_delete_endpoint'; -import { InferenceEndpointUI } from '../../../../types'; -import type { UseActionProps } from '../types'; - -export const useDeleteAction = ({ onActionSuccess }: UseActionProps) => { - const [isModalVisible, setIsModalVisible] = useState(false); - const [endpointToBeDeleted, setEndpointToBeDeleted] = useState(null); - const onCloseModal = useCallback(() => setIsModalVisible(false), []); - const openModal = useCallback( - (selectedEndpoint: InferenceEndpointUI) => { - onActionSuccess(); - setIsModalVisible(true); - setEndpointToBeDeleted(selectedEndpoint); - }, - [onActionSuccess] - ); - - const { mutate: deleteEndpoint } = useDeleteEndpoint(); - - const onConfirmDeletion = useCallback(() => { - onCloseModal(); - if (!endpointToBeDeleted) { - return; - } - - deleteEndpoint({ - type: endpointToBeDeleted.type, - id: endpointToBeDeleted.endpoint.model_id, - }); - }, [deleteEndpoint, onCloseModal, endpointToBeDeleted]); - - const getAction = (selectedEndpoint: InferenceEndpointUI) => { - return ( - } - onClick={() => openModal(selectedEndpoint)} - > - {i18n.DELETE_ACTION_LABEL} - - ); - }; - - return { getAction, isModalVisible, onConfirmDeletion, onCloseModal }; -}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/types.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/types.ts deleted file mode 100644 index a80ccae703b7af..00000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/types.ts +++ /dev/null @@ -1,12 +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 interface UseActionProps { - onActionSuccess: () => void; -} - -export type UseCopyIDActionProps = Pick; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/use_actions.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/use_actions.tsx deleted file mode 100644 index f76d147c68646b..00000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/use_actions.tsx +++ /dev/null @@ -1,79 +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 type { EuiTableComputedColumnType } from '@elastic/eui'; -import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; -import { InferenceEndpointUI } from '../../types'; -import { useCopyIDAction } from './actions/copy_id/use_copy_id_action'; -import { ConfirmDeleteEndpointModal } from './actions/delete/confirm_delete_endpoint'; -import { useDeleteAction } from './actions/delete/use_delete_action'; - -export const ActionColumn: React.FC<{ interfaceEndpoint: InferenceEndpointUI }> = ({ - interfaceEndpoint, -}) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const tooglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); - const closePopover = useCallback(() => setIsPopoverOpen(false), []); - - const copyIDAction = useCopyIDAction({ - onActionSuccess: closePopover, - }); - - const deleteAction = useDeleteAction({ - onActionSuccess: closePopover, - }); - - const items = [ - copyIDAction.getAction(interfaceEndpoint), - deleteAction.getAction(interfaceEndpoint), - ]; - - return ( - <> - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - - {deleteAction.isModalVisible ? ( - - ) : null} - - ); -}; - -interface UseBulkActionsReturnValue { - actions: EuiTableComputedColumnType; -} - -export const useActions = (): UseBulkActionsReturnValue => { - return { - actions: { - align: 'right', - render: (interfaceEndpoint: InferenceEndpointUI) => { - return ; - }, - width: '165px', - }, - }; -}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_deployment_status/deployment_status.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_deployment_status/deployment_status.tsx index 61e6e620d24856..cd2466e483ac26 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_deployment_status/deployment_status.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_deployment_status/deployment_status.tsx @@ -15,7 +15,7 @@ interface DeploymentStatusProps { } export const DeploymentStatus: React.FC = ({ status }) => { - if (status === DeploymentStatusEnum.notApplicable) { + if (status === DeploymentStatusEnum.notApplicable || !status) { return null; } 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 25892236d61f75..1cb34a06ff9978 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 @@ -21,18 +21,18 @@ export interface EndpointInfoProps { export const EndpointInfo: React.FC = ({ endpoint }) => { return ( - - + + {endpoint.model_id} - + ); }; -export const EndpointModelInfo: React.FC = ({ endpoint }) => { +const EndpointModelInfo: React.FC = ({ endpoint }) => { const serviceSettings = endpoint.service_settings; const modelId = 'model_id' in serviceSettings @@ -44,15 +44,10 @@ export const EndpointModelInfo: React.FC = ({ endpoint }) => const isEligibleForMITBadge = modelId && ELASTIC_MODEL_DEFINITIONS[modelId]?.license === 'MIT'; return ( - - {modelId && ( - - - - )} - - {isEligibleForMITBadge && ( - + <> + + {modelId && } + {isEligibleForMITBadge ? ( = ({ endpoint }) => > {i18n.MIT_LICENSE} - - )} - - - - {endpointModelAtrributes(endpoint)} - - - + ) : null}{' '} + {endpointModelAtrributes(endpoint)} + + ); }; @@ -141,7 +131,7 @@ function openAIAttributes(endpoint: InferenceAPIConfigResponse) { function azureOpenAIStudioAttributes(endpoint: InferenceAPIConfigResponse) { const serviceSettings = endpoint.service_settings; - const provider = 'provider' in serviceSettings ? serviceSettings.provider : undefined; + const provider = 'provider' in serviceSettings ? serviceSettings?.provider : undefined; const endpointType = 'endpoint_type' in serviceSettings ? serviceSettings.endpoint_type : undefined; const target = 'target' in serviceSettings ? serviceSettings.target : undefined; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/table_columns.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/table_columns.tsx deleted file mode 100644 index 1b53aeb6ada996..00000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/table_columns.tsx +++ /dev/null @@ -1,80 +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 { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; -import React from 'react'; -import type { HorizontalAlignment } from '@elastic/eui'; -import { TaskTypes } from '../../../../common/types'; -import * as i18n from '../../../../common/translations'; -import { useActions } from './render_actions/use_actions'; -import { EndpointInfo } from './render_endpoint/endpoint_info'; -import { ServiceProvider } from './render_service_provider/service_provider'; -import { TaskType } from './render_task_type/task_type'; -import { DeploymentStatus } from './render_deployment_status/deployment_status'; -import { DeploymentStatusEnum, ServiceProviderKeys } from '../types'; - -export const useTableColumns = () => { - const { actions } = useActions(); - const deploymentAlignment: HorizontalAlignment = 'center'; - - const TABLE_COLUMNS = [ - { - field: 'deployment', - name: '', - render: (deployment: DeploymentStatusEnum) => { - if (deployment != null) { - return ; - } - - return null; - }, - width: '64px', - align: deploymentAlignment, - }, - { - field: 'endpoint', - name: i18n.ENDPOINT, - render: (endpoint: InferenceAPIConfigResponse) => { - if (endpoint != null) { - return ; - } - - return null; - }, - sortable: true, - }, - { - field: 'provider', - name: i18n.SERVICE_PROVIDER, - render: (provider: ServiceProviderKeys) => { - if (provider != null) { - return ; - } - - return null; - }, - sortable: false, - width: '185px', - }, - { - field: 'type', - name: i18n.TASK_TYPE, - render: (type: TaskTypes) => { - if (type != null) { - return ; - } - - return null; - }, - sortable: false, - width: '185px', - }, - actions, - ]; - - return TABLE_COLUMNS; -}; 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 91a2ea959fdec4..70791287d9ae1a 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 @@ -10,6 +10,8 @@ import { screen } from '@testing-library/react'; import { render } from '@testing-library/react'; import { TabularPage } from './tabular_page'; import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; +import { TRAINED_MODEL_STATS_QUERY_KEY } from '../../../common/constants'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const inferenceEndpoints = [ { @@ -43,8 +45,15 @@ jest.mock('../../hooks/use_delete_endpoint', () => ({ })); describe('When the tabular page is loaded', () => { + const queryClient = new QueryClient(); + queryClient.setQueryData([TRAINED_MODEL_STATS_QUERY_KEY], { + trained_model_stats: [{ model_id: '.elser_model_2', deployment_stats: { state: 'started' } }], + }); + const wrapper = ({ children }: { children: React.ReactNode }) => { + return {children}; + }; beforeEach(() => { - render(); + render(wrapper({ children: })); }); it('should display all model_ids in the table', () => { 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 ec19ad49ad477b..cb3b8856594173 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 @@ -5,25 +5,34 @@ * 2.0. */ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + HorizontalAlignment, +} from '@elastic/eui'; import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; -import { extractErrorProperties } from '@kbn/ml-error-utils'; +import { TaskTypes } from '../../../common/types'; import * as i18n from '../../../common/translations'; import { useTableData } from '../../hooks/use_table_data'; -import { FilterOptions } from './types'; +import { FilterOptions, InferenceEndpointUI, ServiceProviderKeys } from './types'; import { DeploymentStatusEnum } from './types'; import { useAllInferenceEndpointsState } from '../../hooks/use_all_inference_endpoints_state'; -import { EndpointsTable } from './endpoints_table'; import { ServiceProviderFilter } from './filter/service_provider_filter'; import { TaskTypeFilter } from './filter/task_type_filter'; import { TableSearch } from './search/table_search'; -import { useTableColumns } from './render_table_columns/table_columns'; -import { useKibana } from '../../hooks/use_kibana'; +import { DeploymentStatus } from './render_table_columns/render_deployment_status/deployment_status'; +import { EndpointInfo } from './render_table_columns/render_endpoint/endpoint_info'; +import { ServiceProvider } from './render_table_columns/render_service_provider/service_provider'; +import { TaskType } from './render_table_columns/render_task_type/task_type'; +import { DeleteAction } from './render_table_columns/render_actions/actions/delete/delete_action'; +import { CopyIDAction } from './render_table_columns/render_actions/actions/copy_id/copy_id_action'; interface TabularPageProps { inferenceEndpoints: InferenceAPIConfigResponse[]; @@ -31,16 +40,9 @@ interface TabularPageProps { export const TabularPage: React.FC = ({ inferenceEndpoints }) => { const [searchKey, setSearchKey] = React.useState(''); - const [deploymentStatus, setDeploymentStatus] = React.useState< - Record - >({}); const { queryParams, setQueryParams, filterOptions, setFilterOptions } = useAllInferenceEndpointsState(); - const { - services: { ml, notifications }, - } = useKibana(); - const onFilterChangedCallback = useCallback( (newFilterOptions: Partial) => { setFilterOptions(newFilterOptions); @@ -48,43 +50,76 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) [setFilterOptions] ); - useEffect(() => { - const fetchDeploymentStatus = async () => { - const trainedModelStats = await ml?.mlApi?.trainedModels.getTrainedModelStats(); - if (trainedModelStats) { - const newDeploymentStatus = trainedModelStats?.trained_model_stats.reduce( - (acc, modelStat) => { - if (modelStat.model_id) { - acc[modelStat.model_id] = - modelStat?.deployment_stats?.state === 'started' - ? DeploymentStatusEnum.deployed - : DeploymentStatusEnum.notDeployed; - } - return acc; - }, - {} as Record - ); - setDeploymentStatus(newDeploymentStatus); - } - }; - - fetchDeploymentStatus().catch((error) => { - const errorObj = extractErrorProperties(error); - notifications?.toasts?.addError(errorObj.message ? new Error(error.message) : error, { - title: i18n.TRAINED_MODELS_STAT_GATHER_FAILED, - }); - }); - }, [ml, notifications]); - const { paginatedSortedTableData, pagination, sorting } = useTableData( inferenceEndpoints, queryParams, filterOptions, - searchKey, - deploymentStatus + searchKey ); - const tableColumns = useTableColumns(); + const tableColumns: Array> = [ + { + field: 'deployment', + name: '', + render: (deployment: DeploymentStatusEnum) => , + align: 'center' as HorizontalAlignment, + width: '64px', + }, + { + field: 'endpoint', + name: i18n.ENDPOINT, + render: (endpoint: InferenceAPIConfigResponse) => { + if (endpoint) { + return ; + } + + return null; + }, + sortable: true, + truncateText: true, + }, + { + field: 'provider', + name: i18n.SERVICE_PROVIDER, + render: (provider: ServiceProviderKeys) => { + if (provider) { + return ; + } + + return null; + }, + sortable: false, + width: '185px', + }, + { + field: 'type', + name: i18n.TASK_TYPE, + render: (type: TaskTypes) => { + if (type) { + return ; + } + + return null; + }, + sortable: false, + width: '185px', + }, + { + actions: [ + { + render: (inferenceEndpoint: InferenceEndpointUI) => ( + + ), + }, + { + render: (inferenceEndpoint: InferenceEndpointUI) => ( + + ), + }, + ], + width: '165px', + }, + ]; const handleTableChange = useCallback( ({ page, sort }) => { @@ -123,9 +158,10 @@ export const TabularPage: React.FC = ({ inferenceEndpoints }) - { - const { inferenceEndpoints } = useQueryInferenceEndpoints(); + const { data } = useQueryInferenceEndpoints(); + + const inferenceEndpoints = data || []; return ( <> diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx index aee9fa1dc55ca1..b8a12d8238f9c2 100644 --- a/x-pack/plugins/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx @@ -17,7 +17,7 @@ interface MutationArgs { id: string; } -export const useDeleteEndpoint = () => { +export const useDeleteEndpoint = (onSuccess?: () => void) => { const queryClient = useQueryClient(); const { services } = useKibana(); const toasts = services.notifications?.toasts; @@ -32,6 +32,9 @@ export const useDeleteEndpoint = () => { toasts?.addSuccess({ title: i18n.DELETE_SUCCESS, }); + if (onSuccess) { + onSuccess(); + } }, onError: (error: { body: KibanaServerError }) => { toasts?.addError(new Error(error.body.message), { diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts index f400429bec2507..1a6435cd25153b 100644 --- a/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts @@ -11,13 +11,10 @@ import { APIRoutes } from '../types'; import { useKibana } from './use_kibana'; import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants'; -export const useQueryInferenceEndpoints = (): { - inferenceEndpoints: InferenceAPIConfigResponse[]; - isLoading: boolean; -} => { +export const useQueryInferenceEndpoints = () => { const { services } = useKibana(); - const { data, isLoading } = useQuery({ + return useQuery({ queryKey: [INFERENCE_ENDPOINTS_QUERY_KEY], queryFn: async () => { const response = await services.http.get<{ @@ -27,6 +24,4 @@ export const useQueryInferenceEndpoints = (): { return response.inference_endpoints; }, }); - - return { inferenceEndpoints: data || [], isLoading }; }; diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx index a8d0326a4c36fd..df058df72fb42d 100644 --- a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx @@ -10,6 +10,9 @@ import { renderHook } from '@testing-library/react-hooks'; import { QueryParams } from '../components/all_inference_endpoints/types'; import { useTableData } from './use_table_data'; import { INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES } from '../components/all_inference_endpoints/types'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; +import { TRAINED_MODEL_STATS_QUERY_KEY } from '../../common/constants'; const inferenceEndpoints = [ { @@ -59,17 +62,23 @@ const filterOptions = { type: ['sparse_embedding', 'text_embedding'], } as any; -const deploymentStatus = { - '.elser_model_2': 'deployed', - lang_ident_model_1: 'not_deployed', -} as any; - const searchKey = 'my'; describe('useTableData', () => { + const queryClient = new QueryClient(); + const wrapper = ({ children }: { children: React.ReactNode }) => { + return {children}; + }; + + beforeEach(() => { + queryClient.setQueryData([TRAINED_MODEL_STATS_QUERY_KEY], { + trained_model_stats: [{ model_id: '.elser_model_2', deployment_stats: { state: 'started' } }], + }); + }); it('should return correct pagination', () => { - const { result } = renderHook(() => - useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey, deploymentStatus) + const { result } = renderHook( + () => useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey), + { wrapper } ); expect(result.current.pagination).toEqual({ @@ -81,8 +90,9 @@ describe('useTableData', () => { }); it('should return correct sorting', () => { - const { result } = renderHook(() => - useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey, deploymentStatus) + const { result } = renderHook( + () => useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey), + { wrapper } ); expect(result.current.sorting).toEqual({ @@ -94,8 +104,9 @@ describe('useTableData', () => { }); it('should return correctly sorted data', () => { - const { result } = renderHook(() => - useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey, deploymentStatus) + const { result } = renderHook( + () => useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey), + { wrapper } ); const expectedSortedData = [...inferenceEndpoints].sort((a, b) => @@ -113,8 +124,9 @@ describe('useTableData', () => { provider: ['elser'], type: ['text_embedding'], } as any; - const { result } = renderHook(() => - useTableData(inferenceEndpoints, queryParams, filterOptions2, searchKey, deploymentStatus) + const { result } = renderHook( + () => useTableData(inferenceEndpoints, queryParams, filterOptions2, searchKey), + { wrapper } ); const filteredData = result.current.sortedTableData; @@ -129,16 +141,18 @@ describe('useTableData', () => { it('should filter data based on searchKey', () => { const searchKey2 = 'model-05'; - const { result } = renderHook(() => - useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey2, deploymentStatus) + const { result } = renderHook( + () => useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey2), + { wrapper } ); const filteredData = result.current.sortedTableData; expect(filteredData.every((item) => item.endpoint.model_id.includes(searchKey))).toBeTruthy(); }); it('should update deployment status based on deploymentStatus object', () => { - const { result } = renderHook(() => - useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey, deploymentStatus) + const { result } = renderHook( + () => useTableData(inferenceEndpoints, queryParams, filterOptions, searchKey), + { wrapper } ); const updatedData = result.current.sortedTableData; diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx index 663df5547c600f..1b3ce7814891f7 100644 --- a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx @@ -20,6 +20,7 @@ import { ServiceProviderKeys, } from '../components/all_inference_endpoints/types'; import { DeploymentStatusEnum } from '../components/all_inference_endpoints/types'; +import { useTrainedModelStats } from './use_trained_model_stats'; interface UseTableDataReturn { tableData: InferenceEndpointUI[]; @@ -33,9 +34,20 @@ export const useTableData = ( inferenceEndpoints: InferenceAPIConfigResponse[], queryParams: QueryParams, filterOptions: FilterOptions, - searchKey: string, - deploymentStatus: Record + searchKey: string ): UseTableDataReturn => { + const { data: trainedModelStats } = useTrainedModelStats(); + + const deploymentStatus = trainedModelStats?.trained_model_stats.reduce((acc, modelStat) => { + if (modelStat.model_id) { + acc[modelStat.model_id] = + modelStat?.deployment_stats?.state === 'started' + ? DeploymentStatusEnum.deployed + : DeploymentStatusEnum.notDeployed; + } + return acc; + }, {} as Record); + const tableData: InferenceEndpointUI[] = useMemo(() => { let filteredEndpoints = inferenceEndpoints; @@ -62,7 +74,7 @@ export const useTableData = ( if (isElasticService) { const modelId = endpoint.service_settings?.model_id; deploymentStatusValue = - modelId && deploymentStatus[modelId] !== undefined + modelId && deploymentStatus?.[modelId] ? deploymentStatus[modelId] : DeploymentStatusEnum.notDeployable; } diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_trained_model_stats.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_trained_model_stats.ts new file mode 100644 index 00000000000000..57b643b78dc3a4 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_trained_model_stats.ts @@ -0,0 +1,24 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { InferenceStatsResponse } from '@kbn/ml-plugin/public/application/services/ml_api_service/trained_models'; +import { useKibana } from './use_kibana'; +import { TRAINED_MODEL_STATS_QUERY_KEY } from '../../common/constants'; + +export const useTrainedModelStats = () => { + const { services } = useKibana(); + + return useQuery({ + queryKey: [TRAINED_MODEL_STATS_QUERY_KEY], + queryFn: async () => { + const response = await services.ml?.mlApi?.trainedModels.getTrainedModelStats(); + + return response || ({ count: 0, trained_model_stats: [] } as InferenceStatsResponse); + }, + }); +}; diff --git a/x-pack/plugins/search_inference_endpoints/tsconfig.json b/x-pack/plugins/search_inference_endpoints/tsconfig.json index 6094924306e541..e915df9529b6b4 100644 --- a/x-pack/plugins/search_inference_endpoints/tsconfig.json +++ b/x-pack/plugins/search_inference_endpoints/tsconfig.json @@ -4,9 +4,9 @@ "outDir": "target/types", }, "include": [ - "__mocks__/**/*", - "common/**/*", - "public/**/*", + "__mocks__/**/*", + "common/**/*", + "public/**/*", "server/**/*" ], "kbn_references": [ @@ -29,7 +29,6 @@ "@kbn/doc-links", "@kbn/console-plugin", "@kbn/test-jest-helpers", - "@kbn/ml-error-utils", "@kbn/kibana-utils-plugin" ], "exclude": [