From 88cf632da91dbbfc7e119cdc6d930baed57de4e6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 9 Oct 2024 10:34:05 +0200 Subject: [PATCH] [ML] Updates for Trained Models table layout and model states (#194614) ## Summary - Updates Trained Models table layout - Adds the E5 model disclaimer - Removes redundant success toasts about model download, deletion, and start of a deployment image ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [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) --- .../src/constants/trained_models.ts | 9 + .../analytics_list/use_table_settings.ts | 47 ++- .../data_drift/data_view_editor.tsx | 3 +- .../model_management/delete_models_modal.tsx | 12 +- .../model_management/get_model_state.tsx | 62 ++- .../model_management/model_actions.tsx | 103 ++--- .../model_management/models_list.tsx | 358 ++++++++---------- .../model_management/model_provider.test.ts | 8 + .../translations/translations/fr-FR.json | 9 - .../translations/translations/ja-JP.json | 8 - .../translations/translations/zh-CN.json | 9 - .../apis/ml/trained_models/model_downloads.ts | 4 + .../services/ml/trained_models_table.ts | 18 +- 13 files changed, 335 insertions(+), 315 deletions(-) diff --git a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts index 95337518361e9..9fd3483771a9f 100644 --- a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts +++ b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts @@ -120,6 +120,10 @@ export const ELASTIC_MODEL_DEFINITIONS: Record< license: 'MIT', licenseUrl: 'https://huggingface.co/elastic/multilingual-e5-small', type: ['pytorch', 'text_embedding'], + disclaimer: i18n.translate('xpack.ml.trainedModels.modelsList.e5v1Disclaimer', { + defaultMessage: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', + }), }, [E5_LINUX_OPTIMIZED_MODEL_ID]: { modelName: 'e5', @@ -138,6 +142,10 @@ export const ELASTIC_MODEL_DEFINITIONS: Record< license: 'MIT', licenseUrl: 'https://huggingface.co/elastic/multilingual-e5-small_linux-x86_64', type: ['pytorch', 'text_embedding'], + disclaimer: i18n.translate('xpack.ml.trainedModels.modelsList.e5v1Disclaimer', { + defaultMessage: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', + }), }, } as const); @@ -167,6 +175,7 @@ export interface ModelDefinition { /** Link to the external license/documentation page */ licenseUrl?: string; type?: readonly string[]; + disclaimer?: string; } export type ModelDefinitionResponse = ModelDefinition & { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts index 670efb1627ef7..fa24a65c425bc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -30,9 +30,11 @@ export interface CriteriaWithPagination extends Criteria { }; } -interface UseTableSettingsReturnValue { +interface UseTableSettingsReturnValue { onTableChange: EuiBasicTableProps['onChange']; - pagination: Required>; + pagination: HidePagination extends true + ? Required> | boolean + : Required>; sorting: { sort: { field: keyof T; @@ -44,8 +46,31 @@ interface UseTableSettingsReturnValue { export function useTableSettings( totalItemCount: number, pageState: ListingPageUrlState, - updatePageState: (update: Partial) => void -): UseTableSettingsReturnValue { + updatePageState: (update: Partial) => void, + hide: true +): UseTableSettingsReturnValue; + +export function useTableSettings( + totalItemCount: number, + pageState: ListingPageUrlState, + updatePageState: (update: Partial) => void, + hide?: false +): UseTableSettingsReturnValue; + +/** + * + * @param totalItemCount + * @param pageState + * @param updatePageState + * @param hide If true, hides pagination when total number of items is lower that the smallest per page option + * @returns + */ +export function useTableSettings( + totalItemCount: number, + pageState: ListingPageUrlState, + updatePageState: (update: Partial) => void, + hide: boolean = false +): UseTableSettingsReturnValue { const { pageIndex, pageSize, sortField, sortDirection } = pageState; const onTableChange: EuiBasicTableProps['onChange'] = useCallback( @@ -66,15 +91,19 @@ export function useTableSettings( [pageState, updatePageState] ); - const pagination = useMemo( - () => ({ + const pagination = useMemo(() => { + if (hide && totalItemCount <= Math.min(...PAGE_SIZE_OPTIONS)) { + // Hide pagination if total number of items is lower that the smallest per page option + return false; + } + + return { pageIndex, pageSize, totalItemCount, pageSizeOptions: PAGE_SIZE_OPTIONS, - }), - [totalItemCount, pageIndex, pageSize] - ); + }; + }, [totalItemCount, pageIndex, pageSize, hide]); const sorting = useMemo( () => ({ diff --git a/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_view_editor.tsx b/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_view_editor.tsx index 5f52ef1c928f8..eafe31cb0f355 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_view_editor.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_view_editor.tsx @@ -80,8 +80,7 @@ export function DataViewEditor({ const { onTableChange, pagination } = useTableSettings( matchedReferenceIndices.length, pageState, - // @ts-expect-error callback will have all the 4 necessary params - updatePageState + updatePageState as Parameters['2'] ); const pageOfItems = useMemo(() => { diff --git a/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx index 1e08ae9874567..0f5c515c22776 100644 --- a/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx @@ -35,7 +35,7 @@ interface DeleteModelsModalProps { export const DeleteModelsModal: FC = ({ models, onClose }) => { const trainedModelsApiService = useTrainedModelsApiService(); - const { displayErrorToast, displaySuccessToast } = useToastNotificationService(); + const { displayErrorToast } = useToastNotificationService(); const [canDeleteModel, setCanDeleteModel] = useState(false); const [deletePipelines, setDeletePipelines] = useState(false); @@ -66,16 +66,6 @@ export const DeleteModelsModal: FC = ({ models, onClose }) ) ); - displaySuccessToast( - i18n.translate('xpack.ml.trainedModels.modelsList.successfullyDeletedMessage', { - defaultMessage: - '{modelsCount, plural, one {Model {modelIds}} other {# models}} {modelsCount, plural, one {has} other {have}} been successfully deleted', - values: { - modelsCount: modelIds.length, - modelIds: modelIds.join(', '), - }, - }) - ); } catch (error) { displayErrorToast( error, diff --git a/x-pack/plugins/ml/public/application/model_management/get_model_state.tsx b/x-pack/plugins/ml/public/application/model_management/get_model_state.tsx index 8591a3b9e8dc9..d8bf2b8084a6a 100644 --- a/x-pack/plugins/ml/public/application/model_management/get_model_state.tsx +++ b/x-pack/plugins/ml/public/application/model_management/get_model_state.tsx @@ -5,8 +5,17 @@ * 2.0. */ +import React from 'react'; import { DEPLOYMENT_STATE, MODEL_STATE, type ModelState } from '@kbn/ml-trained-models-utils'; -import type { EuiHealthProps } from '@elastic/eui'; +import { + EuiBadge, + EuiHealth, + EuiLoadingSpinner, + type EuiHealthProps, + EuiFlexGroup, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { ModelItem } from './models_list'; @@ -33,11 +42,11 @@ export const getModelDeploymentState = (model: ModelItem): ModelState | undefine export const getModelStateColor = ( state: ModelState | undefined -): { color: EuiHealthProps['color']; name: string } | null => { +): { color: EuiHealthProps['color']; name: string; component?: React.ReactNode } | null => { switch (state) { case MODEL_STATE.DOWNLOADED: return { - color: 'subdued', + color: 'success', name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.downloadedName', { defaultMessage: 'Ready to deploy', }), @@ -46,37 +55,64 @@ export const getModelStateColor = ( return { color: 'primary', name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.downloadingName', { - defaultMessage: 'Downloading...', + defaultMessage: 'Downloading', }), }; case MODEL_STATE.STARTED: return { - color: 'success', + color: '#E6F9F7', name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.startedName', { defaultMessage: 'Deployed', }), + get component() { + return ( + + + {this.name} + + + ); + }, }; case MODEL_STATE.STARTING: return { color: 'success', name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.startingName', { - defaultMessage: 'Starting deployment...', + defaultMessage: 'Deploying', }), + get component() { + return ( + + + + + + {this.name} + + + ); + }, }; case MODEL_STATE.STOPPING: return { color: 'accent', name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.stoppingName', { - defaultMessage: 'Stopping deployment...', + defaultMessage: 'Stopping', }), + get component() { + return ( + + + + + + {this.name} + + + ); + }, }; case MODEL_STATE.NOT_DOWNLOADED: - return { - color: '#d4dae5', - name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.notDownloadedName', { - defaultMessage: 'Not downloaded', - }), - }; default: return null; } diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index b9e0c39578349..b4ddff093933a 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -8,7 +8,7 @@ import type { Action } from '@elastic/eui/src/components/basic_table/action_types'; import { i18n } from '@kbn/i18n'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { EuiToolTip } from '@elastic/eui'; +import { EuiToolTip, useIsWithinMaxBreakpoint } from '@elastic/eui'; import React, { useCallback, useMemo, useEffect, useState } from 'react'; import { BUILT_IN_MODEL_TAG, @@ -53,6 +53,8 @@ export function useModelActions({ fetchModels: () => Promise; modelAndDeploymentIds: string[]; }): Array> { + const isMobileLayout = useIsWithinMaxBreakpoint('l'); + const { services: { application: { navigateToUrl }, @@ -132,7 +134,7 @@ export function useModelActions({ [] ); - return useMemo( + return useMemo>>( () => [ { name: i18n.translate('xpack.ml.trainedModels.modelsList.viewTrainingDataNameActionLabel', { @@ -203,12 +205,18 @@ export function useModelActions({ ), 'data-test-subj': 'mlModelsTableRowStartDeploymentAction', icon: 'play', - type: 'icon', + // @ts-ignore + type: isMobileLayout ? 'icon' : 'button', isPrimary: true, + color: 'success', enabled: (item) => { - return canStartStopTrainedModels && !isLoading && item.state !== MODEL_STATE.DOWNLOADING; + return canStartStopTrainedModels && !isLoading; + }, + available: (item) => { + return ( + item.model_type === TRAINED_MODEL_TYPE.PYTORCH && item.state === MODEL_STATE.DOWNLOADED + ); }, - available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH, onClick: async (item) => { const modelDeploymentParams = await getUserInputModelDeploymentParams( item, @@ -234,14 +242,6 @@ export function useModelActions({ : {}), } ); - displaySuccessToast( - i18n.translate('xpack.ml.trainedModels.modelsList.startSuccess', { - defaultMessage: 'Deployment for "{modelId}" has been started successfully.', - values: { - modelId: item.model_id, - }, - }) - ); await fetchModels(); } catch (e) { displayErrorToast( @@ -342,6 +342,7 @@ export function useModelActions({ available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH && canStartStopTrainedModels && + // Deployment can be either started, starting, or exist in a failed state (item.state === MODEL_STATE.STARTED || item.state === MODEL_STATE.STARTING) && // Only show the action if there is at least one deployment that is not used by the inference service (!Array.isArray(item.inference_apis) || @@ -373,16 +374,6 @@ export function useModelActions({ force: requireForceStop, } ); - displaySuccessToast( - i18n.translate('xpack.ml.trainedModels.modelsList.stopSuccess', { - defaultMessage: - '{numberOfDeployments, plural, one {Deployment} other {Deployments}} for "{modelId}" has been stopped successfully.', - values: { - modelId: item.model_id, - numberOfDeployments: deploymentIds.length, - }, - }) - ); if (Object.values(results).some((r) => r.error !== undefined)) { Object.entries(results).forEach(([id, r]) => { if (r.error !== undefined) { @@ -423,7 +414,9 @@ export function useModelActions({ }), 'data-test-subj': 'mlModelsTableRowDownloadModelAction', icon: 'download', - type: 'icon', + color: 'text', + // @ts-ignore + type: isMobileLayout ? 'icon' : 'button', isPrimary: true, available: (item) => canCreateTrainedModels && item.state === MODEL_STATE.NOT_DOWNLOADED, enabled: (item) => !isLoading, @@ -480,10 +473,16 @@ export function useModelActions({ }, { name: (model) => { - return ( + return model.state === MODEL_STATE.DOWNLOADING ? ( + <> + {i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', { + defaultMessage: 'Cancel', + })} + + ) : ( <> {i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', { - defaultMessage: 'Delete model', + defaultMessage: 'Delete', })} ); @@ -491,27 +490,35 @@ export function useModelActions({ description: (model: ModelItem) => { const hasDeployments = model.deployment_ids.length > 0; const { hasInferenceServices } = model; - return hasInferenceServices - ? i18n.translate( - 'xpack.ml.trainedModels.modelsList.deleteDisabledWithInferenceServicesTooltip', - { - defaultMessage: 'Model is used by the _inference API', - } - ) - : hasDeployments - ? i18n.translate( - 'xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip', - { - defaultMessage: 'Model has started deployments', - } - ) - : i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', { - defaultMessage: 'Delete model', - }); + + if (model.state === MODEL_STATE.DOWNLOADING) { + return i18n.translate('xpack.ml.trainedModels.modelsList.cancelDownloadActionLabel', { + defaultMessage: 'Cancel download', + }); + } else if (hasInferenceServices) { + return i18n.translate( + 'xpack.ml.trainedModels.modelsList.deleteDisabledWithInferenceServicesTooltip', + { + defaultMessage: 'Model is used by the _inference API', + } + ); + } else if (hasDeployments) { + return i18n.translate( + 'xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip', + { + defaultMessage: 'Model has started deployments', + } + ); + } else { + return i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', { + defaultMessage: 'Delete model', + }); + } }, 'data-test-subj': 'mlModelsTableRowDeleteAction', icon: 'trash', - type: 'icon', + // @ts-ignore + type: isMobileLayout ? 'icon' : 'button', color: 'danger', isPrimary: false, onClick: (model) => { @@ -539,9 +546,10 @@ export function useModelActions({ }), 'data-test-subj': 'mlModelsTableRowTestAction', icon: 'inputOutput', - type: 'icon', + // @ts-ignore + type: isMobileLayout ? 'icon' : 'button', isPrimary: true, - available: isTestable, + available: (item) => isTestable(item, true), onClick: (item) => { if (isDfaTrainedModel(item) && !isBuiltInModel(item)) { onDfaTestAction(item); @@ -550,7 +558,7 @@ export function useModelActions({ } }, enabled: (item) => { - return canTestTrainedModels && isTestable(item, true) && !isLoading; + return canTestTrainedModels && !isLoading; }, }, { @@ -612,6 +620,7 @@ export function useModelActions({ trainedModelsApiService, urlLocator, onModelDownloadRequest, + isMobileLayout, ] ); } diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 3aef951f1545e..f218030c65ad3 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -5,9 +5,6 @@ * 2.0. */ -import type { FC } from 'react'; -import { useRef } from 'react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; import type { SearchFilterConfig } from '@elastic/eui'; import { EuiBadge, @@ -16,52 +13,43 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiHealth, EuiIcon, EuiInMemoryTable, EuiLink, EuiProgress, EuiSpacer, EuiSwitch, + EuiText, EuiTitle, EuiToolTip, type EuiSearchBarProps, } from '@elastic/eui'; -import { groupBy, isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import type { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; import type { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; -import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { usePageUrlState } from '@kbn/ml-url-state'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useTimefilter } from '@kbn/ml-date-picker'; -import type { DeploymentState } from '@kbn/ml-trained-models-utils'; +import { isDefined } from '@kbn/ml-is-defined'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { useStorage } from '@kbn/ml-local-storage'; import { BUILT_IN_MODEL_TAG, BUILT_IN_MODEL_TYPE, - DEPLOYMENT_STATE, - ELASTIC_MODEL_DEFINITIONS, ELASTIC_MODEL_TAG, ELASTIC_MODEL_TYPE, ELSER_ID_V1, MODEL_STATE, type ModelState, } from '@kbn/ml-trained-models-utils'; -import { isDefined } from '@kbn/ml-is-defined'; -import { useStorage } from '@kbn/ml-local-storage'; +import type { ListingPageUrlState } from '@kbn/ml-url-state'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { dynamic } from '@kbn/shared-ux-utility'; +import { cloneDeep, groupBy, isEmpty, memoize } from 'lodash'; +import type { FC } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; -import type { ListingPageUrlState } from '@kbn/ml-url-state'; -import { getModelStateColor, getModelDeploymentState } from './get_model_state'; +import { ML_PAGES } from '../../../common/constants/locator'; import { ML_ELSER_CALLOUT_DISMISSED } from '../../../common/types/storage'; -import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; -import { useModelActions } from './model_actions'; -import { ModelsTableToConfigMapping } from './config_mapping'; -import type { ModelsBarStats } from '../components/stats_bar'; -import { StatsBar } from '../components/stats_bar'; -import { useMlKibana } from '../contexts/kibana'; -import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models'; import type { ModelDownloadState, ModelPipelines, @@ -69,17 +57,23 @@ import type { TrainedModelDeploymentStatsResponse, TrainedModelStat, } from '../../../common/types/trained_models'; -import { DeleteModelsModal } from './delete_models_modal'; -import { ML_PAGES } from '../../../common/constants/locator'; +import { AddInferencePipelineFlyout } from '../components/ml_inference'; +import { SavedObjectsWarning } from '../components/saved_objects_warning'; +import type { ModelsBarStats } from '../components/stats_bar'; +import { StatsBar } from '../components/stats_bar'; +import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; +import { useMlKibana } from '../contexts/kibana'; +import { useEnabledFeatures } from '../contexts/ml'; import { useTableSettings } from '../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings'; -import { useToastNotificationService } from '../services/toast_notification_service'; -import { useFieldFormatter } from '../contexts/kibana/use_field_formatter'; import { useRefresh } from '../routing/use_refresh'; -import { SavedObjectsWarning } from '../components/saved_objects_warning'; -import { TestModelAndPipelineCreationFlyout } from './test_models'; +import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models'; +import { useToastNotificationService } from '../services/toast_notification_service'; +import { ModelsTableToConfigMapping } from './config_mapping'; +import { DeleteModelsModal } from './delete_models_modal'; +import { getModelDeploymentState, getModelStateColor } from './get_model_state'; +import { useModelActions } from './model_actions'; import { TestDfaModelsFlyout } from './test_dfa_models_flyout'; -import { AddInferencePipelineFlyout } from '../components/ml_inference'; -import { useEnabledFeatures } from '../contexts/ml'; +import { TestModelAndPipelineCreationFlyout } from './test_models'; type Stats = Omit; @@ -106,6 +100,7 @@ export type ModelItem = TrainedModelConfigResponse & { softwareLicense?: string; licenseUrl?: string; downloadState?: ModelDownloadState; + disclaimer?: string; }; export type ModelItemFull = Required; @@ -165,8 +160,6 @@ export const ModelsList: FC = ({ useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true }); - const dateFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DATE); - // allow for an internally controlled page state which stores the state in the URL // or an external page state, which is passed in as a prop. // external page state is used on the management page. @@ -188,7 +181,7 @@ export const ModelsList: FC = ({ const trainedModelsApiService = useTrainedModelsApiService(); - const { displayErrorToast, displaySuccessToast } = useToastNotificationService(); + const { displayErrorToast } = useToastNotificationService(); const [isInitialized, setIsInitialized] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -219,28 +212,9 @@ export const ModelsList: FC = ({ }, [items]); /** - * Checks if the model download complete. + * Fetch of model definitions available for download needs to happen only once */ - const isDownloadComplete = useCallback( - async (modelId: string): Promise => { - try { - const response = await trainedModelsApiService.getTrainedModels(modelId, { - include: 'definition_status', - }); - // @ts-ignore - return !!response[0]?.fully_defined; - } catch (error) { - displayErrorToast( - error, - i18n.translate('xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage', { - defaultMessage: 'Failed to check download status', - }) - ); - } - return false; - }, - [trainedModelsApiService, displayErrorToast] - ); + const getTrainedModelDownloads = memoize(trainedModelsApiService.getTrainedModelDownloads); /** * Fetches trained models. @@ -288,15 +262,20 @@ export const ModelsList: FC = ({ const idMap = new Map( resultItems.map((model) => [model.model_id, model]) ); - const forDownload = await trainedModelsApiService.getTrainedModelDownloads(); + /** + * Fetches model definitions available for download + */ + const forDownload = await getTrainedModelDownloads(); + const notDownloaded: ModelItem[] = forDownload - .filter(({ model_id: modelId, hidden, recommended, supported }) => { + .filter(({ model_id: modelId, hidden, recommended, supported, disclaimer }) => { if (idMap.has(modelId)) { const model = idMap.get(modelId)!; if (recommended) { model.recommended = true; } model.supported = supported; + model.disclaimer = disclaimer; } return !idMap.has(modelId) && !hidden; }) @@ -315,23 +294,28 @@ export const ModelsList: FC = ({ softwareLicense: modelDefinition.license, licenseUrl: modelDefinition.licenseUrl, supported: modelDefinition.supported, + disclaimer: modelDefinition.disclaimer, } as ModelItem; }); resultItems = [...resultItems, ...notDownloaded]; } - setItems(resultItems); - - if (expandedItemsToRefresh.length > 0) { - await fetchModelsStats(expandedItemsToRefresh); - - setItemIdToExpandedRowMap( - expandedItemsToRefresh.reduce((acc, item) => { - acc[item.model_id] = ; - return acc; - }, {} as Record) - ); - } + setItems((prevItems) => { + // Need to merge existing items with new items + // to preserve state and download status + return resultItems.map((item) => { + const prevItem = prevItems.find((i) => i.model_id === item.model_id); + return { + ...item, + ...(prevItem?.state === MODEL_STATE.DOWNLOADING + ? { + state: prevItem.state, + downloadState: prevItem.downloadState, + } + : {}), + }; + }); + }); } catch (error) { displayErrorToast( error, @@ -340,7 +324,9 @@ export const ModelsList: FC = ({ }) ); } + setIsInitialized(true); + setIsLoading(false); await fetchDownloadStatus(); @@ -399,20 +385,6 @@ export const ModelsList: FC = ({ return c.reason ?? ''; }, ''); }); - - const elasticModels = models.filter((model) => - Object.hasOwn(ELASTIC_MODEL_DEFINITIONS, model.model_id) - ); - if (elasticModels.length > 0) { - for (const model of elasticModels) { - if (Object.values(DEPLOYMENT_STATE).includes(model.state as DeploymentState)) { - // no need to check for the download status if the model has been deployed - continue; - } - const isDownloaded = await isDownloadComplete(model.model_id); - model.state = isDownloaded ? MODEL_STATE.DOWNLOADED : MODEL_STATE.DOWNLOADING; - } - } } return true; @@ -429,6 +401,8 @@ export const ModelsList: FC = ({ }, []); const downLoadStatusFetchInProgress = useRef(false); + const abortedDownload = useRef(new Set()); + /** * Updates model list with download status */ @@ -448,47 +422,43 @@ export const ModelsList: FC = ({ if (isMounted()) { setItems((prevItems) => { return prevItems.map((item) => { - const newItem = { ...item }; + if (!item.type?.includes('pytorch')) { + return item; + } + const newItem = cloneDeep(item); + if (downloadStatus[item.model_id]) { + newItem.state = MODEL_STATE.DOWNLOADING; newItem.downloadState = downloadStatus[item.model_id]; } else { - if (downloadInProgress.has(item.model_id)) { + /* Unfortunately, model download status does not report 100% download state, only from 1 to 99. Hence, there might be 3 cases + * 1. Model is not downloaded at all + * 2. Model download was in progress and finished + * 3. Model download was in progress and aborted + */ + delete newItem.downloadState; + + if (abortedDownload.current.has(item.model_id)) { + // Change downloading state to not downloaded + newItem.state = MODEL_STATE.NOT_DOWNLOADED; + abortedDownload.current.delete(item.model_id); + } else if (downloadInProgress.has(item.model_id) || !newItem.state) { // Change downloading state to downloaded - delete newItem.downloadState; newItem.state = MODEL_STATE.DOWNLOADED; } + + downloadInProgress.delete(item.model_id); } return newItem; }); }); } - const downloadedModelIds = Array.from(downloadInProgress).filter( - (v) => !downloadStatus[v] - ); - - if (downloadedModelIds.length > 0) { - // Show success toast - displaySuccessToast( - i18n.translate('xpack.ml.trainedModels.modelsList.downloadCompleteSuccess', { - defaultMessage: - '"{modelIds}" {modelIdsLength, plural, one {has} other {have}} been downloaded successfully.', - values: { - modelIds: downloadedModelIds.join(', '), - modelIdsLength: downloadedModelIds.length, - }, - }) - ); - } - Object.keys(downloadStatus).forEach((modelId) => { if (downloadStatus[modelId]) { downloadInProgress.add(modelId); } }); - downloadedModelIds.forEach((v) => { - downloadInProgress.delete(v); - }); if (isEmpty(downloadStatus)) { downLoadStatusFetchInProgress.current = false; @@ -501,7 +471,7 @@ export const ModelsList: FC = ({ downLoadStatusFetchInProgress.current = false; } }, - [trainedModelsApiService, displaySuccessToast, isMounted] + [trainedModelsApiService, isMounted] ); /** @@ -575,7 +545,6 @@ export const ModelsList: FC = ({ if (itemIdToExpandedRowMapValues[item.model_id]) { delete itemIdToExpandedRowMapValues[item.model_id]; } else { - await fetchModelsStats([item]); itemIdToExpandedRowMapValues[item.model_id] = ; } setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); @@ -583,9 +552,8 @@ export const ModelsList: FC = ({ const columns: Array> = [ { - align: 'left', - width: '32px', isExpander: true, + align: 'center', render: (item: ModelItem) => { if (!item.stats) { return null; @@ -610,35 +578,25 @@ export const ModelsList: FC = ({ }, { name: modelIdColumnName, - width: '15%', sortable: ({ model_id: modelId }: ModelItem) => modelId, truncateText: false, textOnly: false, 'data-test-subj': 'mlModelsTableColumnId', - render: ({ description, model_id: modelId }: ModelItem) => { + render: ({ + description, + model_id: modelId, + recommended, + supported, + type, + disclaimer, + }: ModelItem) => { const isTechPreview = description?.includes('(Tech Preview)'); - return ( - - {modelId} - {isTechPreview ? ( - - - - ) : null} - - ); - }, - }, - { - name: i18n.translate('xpack.ml.trainedModels.modelsList.modelDescriptionHeader', { - defaultMessage: 'Description', - }), - truncateText: false, - 'data-test-subj': 'mlModelsTableColumnDescription', - render: ({ description, recommended, tags, supported }: ModelItem) => { - if (!description) return null; - const descriptionText = description.replace('(Tech Preview)', ''); + let descriptionText = description?.replace('(Tech Preview)', ''); + + if (disclaimer) { + descriptionText += '. ' + disclaimer; + } const tooltipContent = supported === false ? ( @@ -653,80 +611,98 @@ export const ModelsList: FC = ({ /> ) : null; - return tooltipContent ? ( - - <> - {descriptionText}  - - - - ) : ( - descriptionText + return ( + + + + {modelId} + + {isTechPreview ? ( + + + + ) : null} + + + {descriptionText ? ( + + {descriptionText} + {tooltipContent ? ( + <> +   + + + + + ) : null} + + ) : null} + + {Array.isArray(type) && type.length > 0 ? ( + + {type.map((t) => ( + + + + {t} + + + + ))} + + ) : null} + ); }, }, { - width: '15%', - field: ModelsTableToConfigMapping.type, - name: i18n.translate('xpack.ml.trainedModels.modelsList.typeHeader', { - defaultMessage: 'Type', - }), - sortable: true, - truncateText: true, - align: 'left', - render: (types: string[]) => ( - - {types.map((type) => ( - - - {type} - - - ))} - - ), - 'data-test-subj': 'mlModelsTableColumnType', - }, - { - width: '10%', name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', { defaultMessage: 'State', }), - align: 'left', truncateText: false, + width: '150px', render: ({ state, downloadState }: ModelItem) => { const config = getModelStateColor(state); if (!config) return null; - const isDownloadInProgress = state === MODEL_STATE.DOWNLOADING && downloadState; + const isProgressbarVisible = state === MODEL_STATE.DOWNLOADING && downloadState; const label = ( - + {config.name} - + ); return ( - {isDownloadInProgress ? ( + {isProgressbarVisible ? ( - {((downloadState.downloaded_parts / downloadState.total_parts) * 100).toFixed( - 0 - ) + '%'} + {downloadState + ? ( + (downloadState.downloaded_parts / downloadState.total_parts) * + 100 + ).toFixed(0) + '%' + : '100%'} } - value={downloadState?.downloaded_parts} - max={downloadState?.total_parts} + value={downloadState?.downloaded_parts ?? 1} + max={downloadState?.total_parts ?? 1} size="xs" color={config.color} /> ) : ( - {label} + + {config.component ?? label} + )} ); @@ -734,21 +710,10 @@ export const ModelsList: FC = ({ 'data-test-subj': 'mlModelsTableColumnDeploymentState', }, { - width: '20%', - field: ModelsTableToConfigMapping.createdAt, - name: i18n.translate('xpack.ml.trainedModels.modelsList.createdAtHeader', { - defaultMessage: 'Created at', - }), - dataType: 'date', - render: (v: number) => dateFormatter(v), - sortable: true, - 'data-test-subj': 'mlModelsTableColumnCreatedAt', - }, - { - width: '15%', name: i18n.translate('xpack.ml.trainedModels.modelsList.actionsHeader', { defaultMessage: 'Actions', }), + width: '200px', actions, 'data-test-subj': 'mlModelsTableColumnActions', }, @@ -836,7 +801,8 @@ export const ModelsList: FC = ({ const { onTableChange, pagination, sorting } = useTableSettings( items.length, pageState, - updatePageState + updatePageState, + true ); const search: EuiSearchBarProps = { @@ -921,6 +887,7 @@ export const ModelsList: FC = ({
+ tableLayout={'auto'} responsiveBreakpoint={'xl'} allowNeutralSort={false} columns={columns} @@ -974,7 +941,14 @@ export const ModelsList: FC = ({ {modelsToDelete.length > 0 && ( { + modelsToDelete.forEach((model) => { + if (model.state === MODEL_STATE.DOWNLOADING) { + abortedDownload.current.add(model.model_id); + } + }); + setModelsToDelete([]); + if (refreshList) { fetchModelsData(); } diff --git a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts index 33530bade5fcf..0b9b93720234d 100644 --- a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts +++ b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts @@ -89,6 +89,8 @@ describe('modelsProvider', () => { { config: { input: { field_names: ['text_field'] } }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', + disclaimer: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', model_id: '.multilingual-e5-small', default: true, supported: true, @@ -103,6 +105,8 @@ describe('modelsProvider', () => { config: { input: { field_names: ['text_field'] } }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', + disclaimer: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', model_id: '.multilingual-e5-small_linux-x86_64', os: 'Linux', recommended: true, @@ -175,6 +179,8 @@ describe('modelsProvider', () => { { config: { input: { field_names: ['text_field'] } }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', + disclaimer: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', model_id: '.multilingual-e5-small', recommended: true, supported: true, @@ -189,6 +195,8 @@ describe('modelsProvider', () => { config: { input: { field_names: ['text_field'] } }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', + disclaimer: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', model_id: '.multilingual-e5-small_linux-x86_64', os: 'Linux', supported: false, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e47a52ec5df58..0ee9a547fc7f1 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -30070,7 +30070,6 @@ "xpack.ml.trainedModels.modelsList.builtInModelLabel": "intégré", "xpack.ml.trainedModels.modelsList.builtInModelMessage": "Modèle intégré", "xpack.ml.trainedModels.modelsList.collapseRow": "Réduire", - "xpack.ml.trainedModels.modelsList.createdAtHeader": "Créé à", "xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip": "Le modèle a commencé à être déployé", "xpack.ml.trainedModels.modelsList.deleteDisabledWithInferenceServicesTooltip": "Le modèle est utilisé par l'API _inference", "xpack.ml.trainedModels.modelsList.deleteModal.approvePipelinesDeletionLabel": "Supprimer {pipelinesCount, plural, one {le pipeline} other {les pipelines}}", @@ -30085,9 +30084,7 @@ "xpack.ml.trainedModels.modelsList.deleteModelsButtonLabel": "Supprimer", "xpack.ml.trainedModels.modelsList.deployModelActionLabel": "Déployer le modèle", "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "Le modèle a des pipelines associés", - "xpack.ml.trainedModels.modelsList.downloadCompleteSuccess": "\"{modelIds}\" {modelIdsLength, plural, one {a été téléchargé} other {ont été téléchargés}} avec succès.", "xpack.ml.trainedModels.modelsList.downloadFailed": "Échec du téléchargement de \"{modelId}\"", - "xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage": "Échec de la vérification du statut du téléchargement", "xpack.ml.trainedModels.modelsList.e5Title": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1Description": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1x86Description": "E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimisé for linux-x86_64", @@ -30125,11 +30122,9 @@ "xpack.ml.trainedModels.modelsList.forceStopDialog.selectDeploymentsLegend": "Sélectionner les déploiements à arrêter", "xpack.ml.trainedModels.modelsList.forceStopDialog.title": "Arrêter {deploymentCount, plural, one {le déploiement} other {les déploiements}} du modèle {modelId} ?", "xpack.ml.trainedModels.modelsList.mitLicenseLabel": "Licence : MIT", - "xpack.ml.trainedModels.modelsList.modelDescriptionHeader": "Description", "xpack.ml.trainedModels.modelsList.modelIdHeader": "ID", "xpack.ml.trainedModels.modelsList.modelState.downloadedName": "Paré au déploiement", "xpack.ml.trainedModels.modelsList.modelState.downloadingName": "Téléchargement...", - "xpack.ml.trainedModels.modelsList.modelState.notDownloadedName": "Non téléchargé", "xpack.ml.trainedModels.modelsList.modelState.startedName": "Déployé", "xpack.ml.trainedModels.modelsList.modelState.startingName": "Déploiement lancé...", "xpack.ml.trainedModels.modelsList.modelState.stoppingName": "Déploiement en phase d'arrêt...", @@ -30156,14 +30151,10 @@ "xpack.ml.trainedModels.modelsList.startDeployment.updateButton": "Mettre à jour", "xpack.ml.trainedModels.modelsList.startDeployment.viewElserDocLink": "Afficher la documentation", "xpack.ml.trainedModels.modelsList.startFailed": "Impossible de démarrer \"{modelId}\"", - "xpack.ml.trainedModels.modelsList.startSuccess": "Le déploiement pour \"{modelId}\" a bien été démarré.", "xpack.ml.trainedModels.modelsList.stateHeader": "État", "xpack.ml.trainedModels.modelsList.stopDeploymentWarning": "Impossible d'arrêter \"{deploymentId}\"", "xpack.ml.trainedModels.modelsList.stopFailed": "Impossible d'arrêter \"{modelId}\"", - "xpack.ml.trainedModels.modelsList.stopSuccess": "{numberOfDeployments, plural, one {Le déploiement} other {Les déploiements}} pour \"{modelId}\" {numberOfDeployments, plural, one {a bien été arrêté} other {ont bien été arrêtés}}.", - "xpack.ml.trainedModels.modelsList.successfullyDeletedMessage": "{modelsCount, plural, one {Le modèle {modelIds}} other {# modèles}} {modelsCount, plural, one {a bien été supprimé} other {ont bien été supprimés}}.", "xpack.ml.trainedModels.modelsList.totalAmountLabel": "Total de modèles entraînés", - "xpack.ml.trainedModels.modelsList.typeHeader": "Type", "xpack.ml.trainedModels.modelsList.updateDeployment.modalTitle": "Mettre à jour le déploiement {modelId}", "xpack.ml.trainedModels.modelsList.updateFailed": "Impossible de mettre à jour \"{modelId}\"", "xpack.ml.trainedModels.modelsList.updateSuccess": "Le déploiement pour \"{modelId}\" a bien été mis à jour.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8e2125deaf18c..d7a24239bfec4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -29817,7 +29817,6 @@ "xpack.ml.trainedModels.modelsList.builtInModelLabel": "ビルトイン", "xpack.ml.trainedModels.modelsList.builtInModelMessage": "ビルトインモデル", "xpack.ml.trainedModels.modelsList.collapseRow": "縮小", - "xpack.ml.trainedModels.modelsList.createdAtHeader": "作成日時:", "xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip": "モデルはデプロイを開始しました", "xpack.ml.trainedModels.modelsList.deleteDisabledWithInferenceServicesTooltip": "モデルは_inference APIによって使用されます。", "xpack.ml.trainedModels.modelsList.deleteModal.approvePipelinesDeletionLabel": "{pipelinesCount, plural, other {パイプライン}}を削除", @@ -29832,9 +29831,7 @@ "xpack.ml.trainedModels.modelsList.deleteModelsButtonLabel": "削除", "xpack.ml.trainedModels.modelsList.deployModelActionLabel": "モデルをデプロイ", "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "モデルにはパイプラインが関連付けられています", - "xpack.ml.trainedModels.modelsList.downloadCompleteSuccess": "\"{modelIds}\" {modelIdsLength, plural, other {が}}正常にダウンロードされました。", "xpack.ml.trainedModels.modelsList.downloadFailed": "\"{modelId}\"をダウンロードできませんでした", - "xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage": "ダウンロードステータスを確認できませんでした", "xpack.ml.trainedModels.modelsList.e5Title": "E5(bidirEctional Encoder rEpresentationsからのEmbEddings)", "xpack.ml.trainedModels.modelsList.e5v1Description": "E5(bidirEctional Encoder rEpresentationsからのEmbEddings)", "xpack.ml.trainedModels.modelsList.e5v1x86Description": "E5(bidirEctional Encoder rEpresentationsからのEmbEddings)、inux-x86_64向けに最適化", @@ -29872,11 +29869,9 @@ "xpack.ml.trainedModels.modelsList.forceStopDialog.selectDeploymentsLegend": "停止するデプロイを選択", "xpack.ml.trainedModels.modelsList.forceStopDialog.title": "モデル{modelId}の{deploymentCount, plural, other {デプロイ}}を停止しますか?", "xpack.ml.trainedModels.modelsList.mitLicenseLabel": "ライセンス:MIT", - "xpack.ml.trainedModels.modelsList.modelDescriptionHeader": "説明", "xpack.ml.trainedModels.modelsList.modelIdHeader": "ID", "xpack.ml.trainedModels.modelsList.modelState.downloadedName": "デプロイできます", "xpack.ml.trainedModels.modelsList.modelState.downloadingName": "ダウンロード中...", - "xpack.ml.trainedModels.modelsList.modelState.notDownloadedName": "未ダウンロード", "xpack.ml.trainedModels.modelsList.modelState.startedName": "デプロイ済み", "xpack.ml.trainedModels.modelsList.modelState.startingName": "デプロイを開始中...", "xpack.ml.trainedModels.modelsList.modelState.stoppingName": "デプロイを停止中...", @@ -29903,13 +29898,10 @@ "xpack.ml.trainedModels.modelsList.startDeployment.updateButton": "更新", "xpack.ml.trainedModels.modelsList.startDeployment.viewElserDocLink": "ドキュメンテーションを表示", "xpack.ml.trainedModels.modelsList.startFailed": "\"{modelId}\"の開始に失敗しました", - "xpack.ml.trainedModels.modelsList.startSuccess": "\"{modelId}\"のデプロイが正常に開始しました。", "xpack.ml.trainedModels.modelsList.stateHeader": "ステータス", "xpack.ml.trainedModels.modelsList.stopDeploymentWarning": "\"{deploymentId}\"を停止できませんでした", "xpack.ml.trainedModels.modelsList.stopFailed": "\"{modelId}\"の停止に失敗しました", - "xpack.ml.trainedModels.modelsList.stopSuccess": "\"{modelId}\"の{numberOfDeployments, plural, other {デプロイ}}が正常に停止しました。", "xpack.ml.trainedModels.modelsList.totalAmountLabel": "学習済みモデルの合計数", - "xpack.ml.trainedModels.modelsList.typeHeader": "型", "xpack.ml.trainedModels.modelsList.updateDeployment.modalTitle": "{modelId}デプロイを更新", "xpack.ml.trainedModels.modelsList.updateFailed": "\"{modelId}\"を更新できませんでした", "xpack.ml.trainedModels.modelsList.updateSuccess": "\"{modelId}\"のデプロイが正常に更新されました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3b7a338fab717..8ad1766adf0f6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -29857,7 +29857,6 @@ "xpack.ml.trainedModels.modelsList.builtInModelLabel": "内置", "xpack.ml.trainedModels.modelsList.builtInModelMessage": "内置模型", "xpack.ml.trainedModels.modelsList.collapseRow": "折叠", - "xpack.ml.trainedModels.modelsList.createdAtHeader": "创建于", "xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip": "模型已开始部署", "xpack.ml.trainedModels.modelsList.deleteDisabledWithInferenceServicesTooltip": "模型由 _inference API 使用", "xpack.ml.trainedModels.modelsList.deleteModal.approvePipelinesDeletionLabel": "删除{pipelinesCount, plural, other {管道}}", @@ -29872,9 +29871,7 @@ "xpack.ml.trainedModels.modelsList.deleteModelsButtonLabel": "删除", "xpack.ml.trainedModels.modelsList.deployModelActionLabel": "部署模型", "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "模型有关联的管道", - "xpack.ml.trainedModels.modelsList.downloadCompleteSuccess": "“{modelIds}”{modelIdsLength, plural, other {已}}成功下载。", "xpack.ml.trainedModels.modelsList.downloadFailed": "无法下载“{modelId}”", - "xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage": "无法检查下载状态", "xpack.ml.trainedModels.modelsList.e5Title": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1Description": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1x86Description": "针对 linux-x86_64 进行了优化的 E5 (EmbEddings from bidirEctional Encoder rEpresentations)", @@ -29912,11 +29909,9 @@ "xpack.ml.trainedModels.modelsList.forceStopDialog.selectDeploymentsLegend": "选择要停止的部署", "xpack.ml.trainedModels.modelsList.forceStopDialog.title": "停止{deploymentCount, plural, other {部署}}模型 {modelId}?", "xpack.ml.trainedModels.modelsList.mitLicenseLabel": "许可证:MIT", - "xpack.ml.trainedModels.modelsList.modelDescriptionHeader": "描述", "xpack.ml.trainedModels.modelsList.modelIdHeader": "ID", "xpack.ml.trainedModels.modelsList.modelState.downloadedName": "准备部署", "xpack.ml.trainedModels.modelsList.modelState.downloadingName": "正在下载......", - "xpack.ml.trainedModels.modelsList.modelState.notDownloadedName": "未下载", "xpack.ml.trainedModels.modelsList.modelState.startedName": "已部署", "xpack.ml.trainedModels.modelsList.modelState.startingName": "开始部署......", "xpack.ml.trainedModels.modelsList.modelState.stoppingName": "停止部署......", @@ -29943,14 +29938,10 @@ "xpack.ml.trainedModels.modelsList.startDeployment.updateButton": "更新", "xpack.ml.trainedModels.modelsList.startDeployment.viewElserDocLink": "查看文档", "xpack.ml.trainedModels.modelsList.startFailed": "无法启动“{modelId}”", - "xpack.ml.trainedModels.modelsList.startSuccess": "已成功启动“{modelId}”的部署。", "xpack.ml.trainedModels.modelsList.stateHeader": "状态", "xpack.ml.trainedModels.modelsList.stopDeploymentWarning": "无法停止“{deploymentId}”", "xpack.ml.trainedModels.modelsList.stopFailed": "无法停止“{modelId}”", - "xpack.ml.trainedModels.modelsList.stopSuccess": "已成功停止“{modelId}”的{numberOfDeployments, plural, other {部署}}。", - "xpack.ml.trainedModels.modelsList.successfullyDeletedMessage": "{modelsCount, plural, one {模型 {modelIds}} other {# 个模型}}{modelsCount, plural, other {已}}成功删除", "xpack.ml.trainedModels.modelsList.totalAmountLabel": "已训练的模型总数", - "xpack.ml.trainedModels.modelsList.typeHeader": "类型", "xpack.ml.trainedModels.modelsList.updateDeployment.modalTitle": "更新 {modelId} 部署", "xpack.ml.trainedModels.modelsList.updateFailed": "无法更新“{modelId}”", "xpack.ml.trainedModels.modelsList.updateSuccess": "已成功更新“{modelId}”的部署。", diff --git a/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts b/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts index 4e229c133b4fd..4e5fd70314495 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts @@ -100,6 +100,8 @@ export default ({ getService }: FtrProviderContext) => { }, }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', + disclaimer: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', license: 'MIT', licenseUrl: 'https://huggingface.co/elastic/multilingual-e5-small', type: ['pytorch', 'text_embedding'], @@ -119,6 +121,8 @@ export default ({ getService }: FtrProviderContext) => { }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', + disclaimer: + 'This E5 model, as defined, hosted, integrated and used in conjunction with our other Elastic Software is covered by our standard warranty.', license: 'MIT', licenseUrl: 'https://huggingface.co/elastic/multilingual-e5-small_linux-x86_64', type: ['pytorch', 'text_embedding'], diff --git a/x-pack/test/functional/services/ml/trained_models_table.ts b/x-pack/test/functional/services/ml/trained_models_table.ts index 990ca6c1ed37a..941308b61c328 100644 --- a/x-pack/test/functional/services/ml/trained_models_table.ts +++ b/x-pack/test/functional/services/ml/trained_models_table.ts @@ -52,17 +52,16 @@ export function TrainedModelsTableProvider( id: string; description: string; modelTypes: string[]; - createdAt: string; state: string; } = { id: $tr .findTestSubject('mlModelsTableColumnId') - .find('.euiTableCellContent') + .findTestSubject('mlModelsTableColumnIdValueId') .text() .trim(), description: $tr - .findTestSubject('mlModelsTableColumnDescription') - .find('.euiTableCellContent') + .findTestSubject('mlModelsTableColumnId') + .findTestSubject('mlModelsTableColumnIdValueDescription') .text() .trim(), modelTypes, @@ -71,11 +70,6 @@ export function TrainedModelsTableProvider( .find('.euiTableCellContent') .text() .trim(), - createdAt: $tr - .findTestSubject('mlModelsTableColumnCreatedAt') - .find('.euiTableCellContent') - .text() - .trim(), }; rows.push(rowObject); @@ -161,12 +155,6 @@ export function TrainedModelsTableProvider( expectedRow.modelTypes )}' (got '${JSON.stringify(modelRow.modelTypes)}')` ); - // 'Created at' will be different on each run, - // so we will just assert that the value is in the expected timestamp format. - expect(modelRow.createdAt).to.match( - /^\w{3}\s\d+,\s\d{4}\s@\s\d{2}:\d{2}:\d{2}\.\d{3}$/, - `Expected trained model row created at time to have same format as 'Dec 5, 2019 @ 12:28:34.594' (got '${modelRow.createdAt}')` - ); } public async assertTableIsPopulated() {