From e796419b33491593a61b7e329bfb49c6d381c5b1 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 11 Aug 2020 13:14:50 +0200 Subject: [PATCH 01/35] [ML] init tabs --- .../analytics_navigation_bar.tsx | 60 +++++++++++++++++++ .../analytics_navigation_bar/index.ts | 7 +++ .../components/models_management/index.ts | 7 +++ .../models_management/models_list.tsx | 11 ++++ .../pages/analytics_management/page.tsx | 15 ++++- .../routes/data_frame_analytics/index.ts | 1 + .../data_frame_analytics/models_list.tsx | 46 ++++++++++++++ 7 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx create mode 100644 x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx new file mode 100644 index 00000000000000..bd59749517052a --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiTab, EuiTabs } from '@elastic/eui'; +import { useNavigateToPath } from '../../../../../contexts/kibana'; + +interface Tab { + id: string; + name: string; + path: string; +} + +export const AnalyticsNavigationBar: FC<{ selectedTabId?: string }> = ({ selectedTabId }) => { + const navigateToPath = useNavigateToPath(); + + const tabs = useMemo( + () => [ + { + id: 'data_frame_analytics', + name: i18n.translate('xpack.ml.dataframe.jobsTabLabel', { + defaultMessage: 'Jobs', + }), + path: '/data_frame_analytics', + }, + { + id: 'models', + name: i18n.translate('xpack.ml.dataframe.modelsTabLabel', { + defaultMessage: 'Models', + }), + path: '/data_frame_analytics/models', + }, + ], + [] + ); + + const onTabClick = useCallback(async (tab: Tab) => { + await navigateToPath(tab.path); + }, []); + + return ( + + {tabs.map((tab) => { + return ( + + {tab.name} + + ); + })} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts new file mode 100644 index 00000000000000..594033f3f8d2cb --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './analytics_navigation_bar'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts new file mode 100644 index 00000000000000..737bf2f2b18f66 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './models_list'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx new file mode 100644 index 00000000000000..d1d7025ab6e3b1 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +export const ModelsList: FC = () => { + return <>; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx index 1e83e6c7d0e03e..171fcff52043d9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useState } from 'react'; +import React, { FC, Fragment, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -21,6 +21,7 @@ import { EuiTitle, } from '@elastic/eui'; +import { useLocation } from 'react-router-dom'; import { NavigationMenu } from '../../../components/navigation_menu'; import { DatePickerWrapper } from '../../../components/navigation_menu/date_picker_wrapper'; import { DataFrameAnalyticsList } from './components/analytics_list'; @@ -28,12 +29,17 @@ import { useRefreshInterval } from './components/analytics_list/use_refresh_inte import { RefreshAnalyticsListButton } from './components/refresh_analytics_list_button'; import { NodeAvailableWarning } from '../../../components/node_available_warning'; import { UpgradeWarning } from '../../../components/upgrade'; +import { AnalyticsNavigationBar } from './components/analytics_navigation_bar'; +import { ModelsList } from './components/models_management'; export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); useRefreshInterval(setBlockRefresh); + const location = useLocation(); + const selectedTabId = useMemo(() => location.pathname.split('/').pop(), [location]); + return ( @@ -81,7 +87,12 @@ export const Page: FC = () => { - + + + {selectedTabId === 'data_frame_analytics' && ( + + )} + {selectedTabId === 'models' && } diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts index 9b6bcc25c8c7eb..c75a8240d28fb2 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts @@ -7,3 +7,4 @@ export * from './analytics_jobs_list'; export * from './analytics_job_exploration'; export * from './analytics_job_creation'; +export * from './models_list'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx new file mode 100644 index 00000000000000..347c55d109c1e0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { NavigateToPath } from '../../../contexts/kibana'; + +import { MlRoute, PageLoader, PageProps } from '../../router'; +import { useResolver } from '../../use_resolver'; +import { basicResolvers } from '../../resolvers'; +import { Page } from '../../../data_frame_analytics/pages/analytics_management'; +import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; + +export const modelsListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({ + path: '/data_frame_analytics/models', + render: (props, deps) => , + breadcrumbs: [ + getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath), + getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath), + { + text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.modelsListLabel', { + defaultMessage: 'Models Management', + }), + href: '', + }, + ], +}); + +const PageWrapper: FC = ({ location, deps }) => { + const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + return ( + + + + ); +}; From 02e17b3302c2c8935bc6ae079677c608883e69cd Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 12 Aug 2020 11:43:26 +0200 Subject: [PATCH 02/35] [ML] init inference API service in UI --- .../services/ml_api_service/inference.ts | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts new file mode 100644 index 00000000000000..b8ea9865bc0ba3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpService } from '../http_service'; +import { basePath } from './index'; + +export interface InferenceQueryParams { + decompress_definition?: boolean; + from?: number; + include_model_definition?: boolean; + size?: number; + tags?: string; +} + +export interface InferenceStatsQueryParams { + from?: number; + size?: number; +} + +export interface InferenceConfigResponse { + trained_model_configs: Array<{ + created_by: string; + create_time: string; + default_field_map: Record; + estimated_heap_memory_usage_bytes: number; + estimated_operations: number; + license_level: string; + metadata: + | { + analysis_config: any; + input: any; + } + | Record; + model_id: string; + tags: string; + version: string; + }>; +} + +export interface InferenceStatsResponse { + count: number; + trained_model_stats: Array<{ + model_id: string; + pipeline_count: number; + ingest?: { + total: { + count: number; + time_in_millis: number; + current: number; + failed: number; + }; + pipelines?: Record< + string, + { + count: number; + time_in_millis: number; + current: number; + failed: number; + processors: Array< + Record< + string, + { + type: string; + stats: { + count: number; + time_in_millis: number; + current: number; + failed: number; + }; + } + > + >; + } + >; + }; + }>; +} + +/** + * Service with APIs calls to perform inference operations. + * @param httpService + */ +export function inferenceApiProvider(httpService: HttpService) { + const apiBasePath = basePath(); + + return { + /** + * Fetches configuration information for a trained inference model. + * + * @param modelId - Model ID, collection of Model IDs or Model ID pattern. + * Fetches all In case nothing is provided. + * @param params - Optional query params + */ + getInferenceModel(modelId?: string | string[], params?: InferenceQueryParams) { + let model = modelId ?? '_all'; + if (Array.isArray(modelId)) { + model = modelId.join(','); + } + + return httpService.http({ + path: `${apiBasePath}/inference/${model}`, + method: 'GET', + }); + }, + + /** + * Fetches usage information for trained inference models. + * + * @param modelId - Model ID, collection of Model IDs or Model ID pattern. + * Fetches all In case nothing is provided. + * @param params - Optional query params + */ + getInferenceModelStats(modelId?: string | string[], params?: InferenceStatsQueryParams) { + let model = modelId ?? '_all'; + if (Array.isArray(modelId)) { + model = modelId.join(','); + } + + return httpService.http({ + path: `${apiBasePath}/inference/${model}/_stats`, + method: 'GET', + }); + }, + }; +} From 66203285f4eb9ff11b59d5fa8c17d1b9386a0a7e Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 12 Aug 2020 16:54:24 +0200 Subject: [PATCH 03/35] [ML] server-side routes --- x-pack/plugins/ml/server/plugin.ts | 3 + x-pack/plugins/ml/server/routes/inference.ts | 70 ++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 x-pack/plugins/ml/server/routes/inference.ts diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 3c3824a785032d..f6d47639ef7c50 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -48,6 +48,7 @@ import { createSharedServices, SharedServices } from './shared_services'; import { getPluginPrivileges } from '../common/types/capabilities'; import { setupCapabilitiesSwitcher } from './lib/capabilities'; import { registerKibanaSettings } from './lib/register_settings'; +import { inferenceRoutes } from './routes/inference'; declare module 'kibana/server' { interface RequestHandlerContext { @@ -172,6 +173,8 @@ export class MlServerPlugin implements Plugin { + try { + const { modelId } = request.params; + const { body } = await client.asInternalUser.ml.getTrainedModels({ + ...(modelId ? { model_id: modelId } : {}), + }); + return response.ok({ + body, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + /** + * @apiGroup Inference + * + * @api {get} /api/ml/inference/{modelId} Get stats of a trained inference model + * @apiName GetInferenceStats + * @apiDescription Retrieves usage information for trained inference models. + */ + router.get( + { + path: '/api/ml/inference/{modelId}/_stats', + validate: false, + options: { + tags: ['access:ml:canAccessML'], + }, + }, + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { + try { + const { modelId } = request.params; + const { body } = await client.asInternalUser.ml.getTrainedModelsStats({ + ...(modelId ? { model_id: modelId } : {}), + }); + return response.ok({ + body, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); +} From 646ef99ae8d6840e413a0318094f025d6d2eeebd Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 12 Aug 2020 19:02:23 +0200 Subject: [PATCH 04/35] [ML] basic table --- .../application/components/stats_bar/index.ts | 2 +- .../components/stats_bar/stats_bar.tsx | 6 +- .../analytics_list/analytics_list.tsx | 1 + .../components/models_management/index.ts | 6 + .../models_management/models_list.tsx | 170 +++++++++++++++++- .../services/ml_api_service/inference.ts | 22 ++- .../plugins/ml/server/client/error_wrapper.ts | 4 +- 7 files changed, 204 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/index.ts b/x-pack/plugins/ml/public/application/components/stats_bar/index.ts index 597975d0b150b9..c8023b13cf74e5 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/index.ts +++ b/x-pack/plugins/ml/public/application/components/stats_bar/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { StatsBar, AnalyticStatsBarStats, JobStatsBarStats } from './stats_bar'; +export { StatsBar, AnalyticStatsBarStats, JobStatsBarStats, ModelsBarStats } from './stats_bar'; diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx index 0bd33a8c99f49c..c4d64e48151b3e 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx @@ -23,7 +23,11 @@ export interface AnalyticStatsBarStats extends Stats { stopped: StatsBarStat; } -export type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats; +export interface ModelsBarStats { + total: StatsBarStat; +} + +export type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats | ModelsBarStats; type StatsKey = keyof StatsBarStats; interface StatsBarProps { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 0652ec5f8acb17..9dcf408a129440 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -271,6 +271,7 @@ export const DataFrameAnalyticsList: FC = ({ return ( <> {modals} + {analyticsStats && ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts index 737bf2f2b18f66..918da429203749 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts @@ -5,3 +5,9 @@ */ export * from './models_list'; + +export enum ModelsTableToConfigMapping { + id = 'model_id', + createdAt = 'create_time', + type = 'metadata.analysis_config.type', +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index d1d7025ab6e3b1..02e480b2433886 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -4,8 +4,174 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + Direction, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiSearchBarProps, + EuiSpacer, +} from '@elastic/eui'; +// @ts-ignore +import { formatDate } from '@elastic/eui/lib/services/format'; +import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; +import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; +import { StatsBar, ModelsBarStats } from '../../../../../components/stats_bar'; +import { + ModelConfigResponse, + useInferenceApiService, +} from '../../../../../services/ml_api_service/inference'; +import { ModelsTableToConfigMapping } from './index'; +import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; export const ModelsList: FC = () => { - return <>; + const inferenceApiService = useInferenceApiService(); + + const [searchQueryText, setSearchQueryText] = useState(''); + + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState(ModelsTableToConfigMapping.id); + const [sortDirection, setSortDirection] = useState('asc'); + + const [isLoading, setIsLoading] = useState(false); + const [modelsStats, setModelsStats] = useState(); + const [items, setItems] = useState([]); + const [selectedModels, setSelectedModels] = useState([]); + + async function fetchData() { + setIsLoading(true); + try { + const response = await inferenceApiService.getInferenceModel(); + setItems(response.trained_model_configs); + + setModelsStats({ + total: { + show: true, + value: response.trained_model_configs.length, + label: i18n.translate('xpack.ml.inference.modelsList.totalAmountLabel', { + defaultMessage: 'Total inference trained models', + }), + }, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + setIsLoading(false); + } + + useEffect(() => { + fetchData(); + }, []); + + const columns: Array> = [ + { + field: ModelsTableToConfigMapping.id, + name: i18n.translate('xpack.ml.inference.modelsList.modelIdHeader', { + defaultMessage: 'Model ID', + }), + sortable: true, + truncateText: true, + }, + { + field: ModelsTableToConfigMapping.type, + name: i18n.translate('xpack.ml.inference.modelsList.typeHeader', { + defaultMessage: 'Type', + }), + sortable: true, + align: 'left', + }, + { + field: ModelsTableToConfigMapping.createdAt, + name: i18n.translate('xpack.ml.inference.modelsList.createdAtHeader', { + defaultMessage: 'Created At', + }), + dataType: 'date', + render: (date: string) => formatDate(date, TIME_FORMAT), + sortable: true, + }, + ]; + + const pagination = { + initialPageIndex: pageIndex, + initialPageSize: pageSize, + totalItemCount: items.length, + pageSizeOptions: [10, 20, 50], + hidePerPageOptions: false, + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + const search: EuiSearchBarProps = { + query: searchQueryText, + onChange: (searchChange) => { + if (searchChange.error !== null) { + return false; + } + setSearchQueryText(searchChange.queryText); + return true; + }, + box: { + incremental: true, + }, + }; + + const onTableChange: EuiInMemoryTable['onTableChange'] = ({ + page = { index: 0, size: 10 }, + sort = { field: ModelsTableToConfigMapping.id, direction: 'asc' }, + }) => { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + }; + + const selection: EuiTableSelectionType = { + onSelectionChange: (selectedItems) => { + setSelectedModels(selectedItems); + }, + }; + return ( + <> + + + {modelsStats && ( + + + + )} + + +
+ ({ + 'data-test-subj': `mlModelsTableRow row-${item.model_id}`, + })} + /> +
+ + ); }; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index b8ea9865bc0ba3..80b6ededd6fb5a 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { useMemo } from 'react'; import { HttpService } from '../http_service'; import { basePath } from './index'; +import { useMlKibana } from '../../contexts/kibana'; export interface InferenceQueryParams { decompress_definition?: boolean; @@ -40,6 +42,8 @@ export interface InferenceConfigResponse { }>; } +export type ModelConfigResponse = InferenceConfigResponse['trained_model_configs'][number]; + export interface InferenceStatsResponse { count: number; trained_model_stats: Array<{ @@ -95,13 +99,13 @@ export function inferenceApiProvider(httpService: HttpService) { * @param params - Optional query params */ getInferenceModel(modelId?: string | string[], params?: InferenceQueryParams) { - let model = modelId ?? '_all'; + let model = modelId ?? ''; if (Array.isArray(modelId)) { model = modelId.join(','); } return httpService.http({ - path: `${apiBasePath}/inference/${model}`, + path: `${apiBasePath}/inference${model && `/${model}`}`, method: 'GET', }); }, @@ -126,3 +130,17 @@ export function inferenceApiProvider(httpService: HttpService) { }, }; } + +type InferenceApiService = ReturnType; + +/** + * Hooks for accessing {@link InferenceApiService} in React components. + */ +export function useInferenceApiService(): InferenceApiService { + const { + services: { + mlServices: { httpService }, + }, + } = useMlKibana(); + return useMemo(() => inferenceApiProvider(httpService), [httpService]); +} diff --git a/x-pack/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts index de53e4d4345a9b..c635eb74e1f19e 100644 --- a/x-pack/plugins/ml/server/client/error_wrapper.ts +++ b/x-pack/plugins/ml/server/client/error_wrapper.ts @@ -8,7 +8,9 @@ import { boomify, isBoom } from 'boom'; import { ResponseError, CustomHttpResponseOptions } from 'kibana/server'; export function wrapError(error: any): CustomHttpResponseOptions { - const boom = isBoom(error) ? error : boomify(error, { statusCode: error.status }); + const boom = isBoom(error) + ? error + : boomify(error, { statusCode: error.status ?? error.statusCode }); const statusCode = boom.output.statusCode; return { body: { From c5a56ca7a6d0dd5ddb3de122dbaf04a61cb31677 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 13 Aug 2020 14:37:37 +0200 Subject: [PATCH 05/35] [ML] support deletion --- .../models_management/delete_models_modal.tsx | 90 +++++++++++++++ .../models_management/models_list.tsx | 104 +++++++++++++++++- .../pages/analytics_management/page.tsx | 2 +- .../services/ml_api_service/inference.ts | 50 ++++----- x-pack/plugins/ml/server/routes/inference.ts | 49 ++++++++- .../server/routes/schemas/inference_schema.ts | 21 ++++ 6 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx create mode 100644 x-pack/plugins/ml/server/routes/schemas/inference_schema.ts diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx new file mode 100644 index 00000000000000..fbcfd36f17c768 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiOverlayMask, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButtonEmpty, + EuiButton, + EuiSpacer, + EuiCallOut, +} from '@elastic/eui'; +import { ModelWithStats } from './models_list'; + +interface DeleteModelsModalProps { + models: ModelWithStats[]; + onClose: (deletionApproved?: boolean) => void; +} + +export const DeleteModelsModal: FC = ({ models, onClose }) => { + const modelsWithPipelines = models + .filter((model) => model.stats.pipeline_count > 0) + .map((model) => model.model_id); + + return ( + + + + + model.model_id).join(', ') }} + /> + + + + + + + {modelsWithPipelines.length > 0 && ( + + + + )} + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 02e480b2433886..4287488532bc89 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -18,13 +18,24 @@ import { import { formatDate } from '@elastic/eui/lib/services/format'; import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; +import { Action } from '@elastic/eui/src/components/basic_table/action_types'; import { StatsBar, ModelsBarStats } from '../../../../../components/stats_bar'; import { ModelConfigResponse, + ModelStats, useInferenceApiService, } from '../../../../../services/ml_api_service/inference'; import { ModelsTableToConfigMapping } from './index'; import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; +import { DeleteModelsModal } from './delete_models_modal'; + +type Stats = Omit; + +export type ModelItem = ModelConfigResponse & { + stats?: Stats; +}; + +export type ModelWithStats = Omit & { stats: Stats }; export const ModelsList: FC = () => { const inferenceApiService = useInferenceApiService(); @@ -38,8 +49,10 @@ export const ModelsList: FC = () => { const [isLoading, setIsLoading] = useState(false); const [modelsStats, setModelsStats] = useState(); - const [items, setItems] = useState([]); - const [selectedModels, setSelectedModels] = useState([]); + const [items, setItems] = useState([]); + const [selectedModels, setSelectedModels] = useState([]); + + const [modelsToDelete, setModelsToDelete] = useState([]); async function fetchData() { setIsLoading(true); @@ -67,7 +80,69 @@ export const ModelsList: FC = () => { fetchData(); }, []); - const columns: Array> = [ + async function prepareModelsForDeletion(models: ModelItem[]) { + // Fetch model stats to check associated pipelines + try { + const { + trained_model_stats: modelsStatsResponse, + } = await inferenceApiService.getInferenceModelStats( + models.filter((model) => model.stats === undefined).map((model) => model.model_id) + ); + for (const { model_id: id, ...stats } of modelsStatsResponse) { + const model = models.find((m) => m.model_id === id); + model!.stats = stats; + } + setModelsToDelete(models as ModelWithStats[]); + setItems([...items]); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + + async function deleteModels() { + try { + await Promise.all( + modelsToDelete.map((model) => inferenceApiService.deleteInferenceModel(model.model_id)) + ); + setItems( + items.filter( + (model) => !modelsToDelete.some((toDelete) => toDelete.model_id === model.model_id) + ) + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + + const actions: Array> = [ + { + name: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', { + defaultMessage: 'View training data', + }), + description: 'Clone this person', + icon: 'list', + type: 'icon', + href: 'temp', + isPrimary: true, + }, + { + name: i18n.translate('xpack.ml.inference.modelsList.deleteModelActionLabel', { + defaultMessage: 'Delete model', + }), + description: 'Delete this person', + icon: 'trash', + type: 'icon', + color: 'danger', + isPrimary: false, + onClick: async (model) => { + await prepareModelsForDeletion([model]); + }, + }, + ]; + + const columns: Array> = [ { field: ModelsTableToConfigMapping.id, name: i18n.translate('xpack.ml.inference.modelsList.modelIdHeader', { @@ -93,6 +168,12 @@ export const ModelsList: FC = () => { render: (date: string) => formatDate(date, TIME_FORMAT), sortable: true, }, + { + name: i18n.translate('xpack.ml.inference.modelsList.actionsHeader', { + defaultMessage: 'Actions', + }), + actions, + }, ]; const pagination = { @@ -123,7 +204,7 @@ export const ModelsList: FC = () => { }, }; - const onTableChange: EuiInMemoryTable['onTableChange'] = ({ + const onTableChange: EuiInMemoryTable['onTableChange'] = ({ page = { index: 0, size: 10 }, sort = { field: ModelsTableToConfigMapping.id, direction: 'asc' }, }) => { @@ -136,7 +217,7 @@ export const ModelsList: FC = () => { setSortDirection(direction); }; - const selection: EuiTableSelectionType = { + const selection: EuiTableSelectionType = { onSelectionChange: (selectedItems) => { setSelectedModels(selectedItems); }, @@ -156,7 +237,7 @@ export const ModelsList: FC = () => { { })} /> + {modelsToDelete.length > 0 && ( + { + if (deletionApproved) { + await deleteModels(); + } + setModelsToDelete([]); + }} + models={modelsToDelete} + /> + )} ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx index 171fcff52043d9..7ffd477039e78f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx @@ -51,7 +51,7 @@ export const Page: FC = () => {

  - >; - } - >; + inference_stats: { + failure_count: number; + inference_count: number; + cache_miss_count: number; + missing_all_fields_count: number; + timestamp: number; }; }>; } +export type ModelStats = InferenceStatsResponse['trained_model_stats'][number]; + /** * Service with APIs calls to perform inference operations. * @param httpService @@ -128,6 +106,18 @@ export function inferenceApiProvider(httpService: HttpService) { method: 'GET', }); }, + + /** + * Deletes an existing trained inference model. + * + * @param modelId - Model ID + */ + deleteInferenceModel(modelId: string) { + return httpService.http({ + path: `${apiBasePath}/inference/${modelId}`, + method: 'DELETE', + }); + }, }; } diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index c5496dad509a0b..0261a29dfa9b42 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -6,19 +6,22 @@ import { RouteInitialization } from '../types'; import { wrapError } from '../client/error_wrapper'; +import { modelIdSchema, optionalModelIdSchema } from './schemas/inference_schema'; export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { /** * @apiGroup Inference * - * @api {get} /api/ml/inference/{modelId} Get info of a trained inference model - * @apiName GetInference + * @api {get} /api/ml/inference/:modelId Get info of a trained inference model + * @apiName GetInferenceModel * @apiDescription Retrieves configuration information for a trained inference model. */ router.get( { path: '/api/ml/inference/{modelId?}', - validate: false, + validate: { + params: optionalModelIdSchema, + }, options: { tags: ['access:ml:canAccessML'], }, @@ -41,14 +44,16 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { /** * @apiGroup Inference * - * @api {get} /api/ml/inference/{modelId} Get stats of a trained inference model - * @apiName GetInferenceStats + * @api {get} /api/ml/inference/:modelId/_stats Get stats of a trained inference model + * @apiName GetInferenceModelStats * @apiDescription Retrieves usage information for trained inference models. */ router.get( { path: '/api/ml/inference/{modelId}/_stats', - validate: false, + validate: { + params: modelIdSchema, + }, options: { tags: ['access:ml:canAccessML'], }, @@ -67,4 +72,36 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { } }) ); + + /** + * @apiGroup Inference + * + * @api {delete} /api/ml/inference/:modelId Get stats of a trained inference model + * @apiName DeleteInferenceModel + * @apiDescription Deletes an existing trained inference model that is currently not referenced by an ingest pipeline. + */ + router.delete( + { + path: '/api/ml/inference/{modelId}', + validate: { + params: modelIdSchema, + }, + options: { + tags: ['access:ml:canDeleteDataFrameAnalytics'], + }, + }, + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { + try { + const { modelId } = request.params; + const { body } = await client.asInternalUser.ml.deleteTrainedModel({ + model_id: modelId, + }); + return response.ok({ + body, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); } diff --git a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts new file mode 100644 index 00000000000000..090bb5a4890630 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const modelIdSchema = schema.object({ + /** + * Model ID + */ + modelId: schema.string(), +}); + +export const optionalModelIdSchema = schema.object({ + /** + * Model ID + */ + modelId: schema.maybe(schema.string()), +}); From 3801a384ea7446f08d8899e8da7b0d8b6603ca35 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 13 Aug 2020 16:03:51 +0200 Subject: [PATCH 06/35] [ML] delete multiple models --- .../models_management/models_list.tsx | 113 ++++++++++++++---- 1 file changed, 91 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 4287488532bc89..7b66af7b6a484d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -4,13 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC, useEffect, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { Direction, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, + EuiTitle, + EuiButton, EuiSearchBarProps, EuiSpacer, } from '@elastic/eui'; @@ -28,6 +31,7 @@ import { import { ModelsTableToConfigMapping } from './index'; import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; import { DeleteModelsModal } from './delete_models_modal'; +import { useNotifications } from '../../../../../contexts/kibana'; type Stats = Omit; @@ -39,6 +43,7 @@ export type ModelWithStats = Omit & { stats: Stats }; export const ModelsList: FC = () => { const inferenceApiService = useInferenceApiService(); + const { toasts } = useNotifications(); const [searchQueryText, setSearchQueryText] = useState(''); @@ -69,9 +74,12 @@ export const ModelsList: FC = () => { }), }, }); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.inference.modelsList.fetchFailedErrorMessage', { + defaultMessage: 'Models fetch failed', + }), + }); } setIsLoading(false); } @@ -80,23 +88,49 @@ export const ModelsList: FC = () => { fetchData(); }, []); + /** + * Fetches models stats and update the original object + */ + const fetchModelsStats = useCallback( + async (models: ModelItem[]) => { + const modelIdsToFetch = models + .filter((model) => model.stats === undefined) + .map((model) => model.model_id); + + // no need to fetch + if (modelIdsToFetch.length === 0) return true; + + try { + const { + trained_model_stats: modelsStatsResponse, + } = await inferenceApiService.getInferenceModelStats(modelIdsToFetch); + for (const { model_id: id, ...stats } of modelsStatsResponse) { + const model = models.find((m) => m.model_id === id); + model!.stats = stats; + } + setItems([...items]); + return true; + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.inference.modelsList.fetchModelStatsErrorMessage', { + defaultMessage: 'Fetch model stats failed', + }), + }); + } + }, + [items] + ); + async function prepareModelsForDeletion(models: ModelItem[]) { // Fetch model stats to check associated pipelines - try { - const { - trained_model_stats: modelsStatsResponse, - } = await inferenceApiService.getInferenceModelStats( - models.filter((model) => model.stats === undefined).map((model) => model.model_id) - ); - for (const { model_id: id, ...stats } of modelsStatsResponse) { - const model = models.find((m) => m.model_id === id); - model!.stats = stats; - } + if (await fetchModelsStats(models)) { setModelsToDelete(models as ModelWithStats[]); - setItems([...items]); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); + } else { + toasts.addDanger( + i18n.translate('xpack.ml.inference.modelsList.unableToDeleteModelsErrorMessage', { + defaultMessage: 'Unable to delete models', + }) + ); } } @@ -110,9 +144,12 @@ export const ModelsList: FC = () => { (model) => !modelsToDelete.some((toDelete) => toDelete.model_id === model.model_id) ) ); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.inference.modelsList.fetchDeletionErrorMessage', { + defaultMessage: 'Model deletion failed', + }), + }); } } @@ -131,7 +168,9 @@ export const ModelsList: FC = () => { name: i18n.translate('xpack.ml.inference.modelsList.deleteModelActionLabel', { defaultMessage: 'Delete model', }), - description: 'Delete this person', + description: i18n.translate('xpack.ml.inference.modelsList.deleteModelActionLabel', { + defaultMessage: 'Delete model', + }), icon: 'trash', type: 'icon', color: 'danger', @@ -202,6 +241,36 @@ export const ModelsList: FC = () => { box: { incremental: true, }, + ...(selectedModels.length > 0 + ? { + toolsLeft: ( + + + +
+ +
+
+
+ + + + + +
+ ), + } + : {}), }; const onTableChange: EuiInMemoryTable['onTableChange'] = ({ From dd2f28c99ac28c2314ef1b2767468f1b98d132de Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 13 Aug 2020 17:20:31 +0200 Subject: [PATCH 07/35] [ML] WIP expanded row --- .../models_management/expanded_row.tsx | 125 ++++++++++++++++++ .../models_management/models_list.tsx | 41 +++++- .../services/ml_api_service/inference.ts | 1 + 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx new file mode 100644 index 00000000000000..9dcd07bd4fa17a --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiDescriptionList, EuiPanel, EuiSpacer, EuiTabbedContent, EuiTitle } from '@elastic/eui'; +import { ModelWithStats } from './models_list'; + +interface ExpandedRowProps { + item: ModelWithStats; +} + +export const ExpandedRow: FC = ({ item }) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { model_id, create_time, created_by, inference_config, stats, ...details } = item; + + const tabs = [ + { + id: 'details', + name: ( + + ), + content: ( + <> + + + +
+ +
+
+ ({ + title, + description: typeof value === 'object' ? JSON.stringify(value) : value, + }))} + /> +
+ + ), + }, + { + id: 'config', + name: ( + + ), + content: ( + <> + + + +
+ +
+
+ ({ + title, + description: typeof value === 'object' ? JSON.stringify(value) : value, + }))} + /> +
+ + ), + }, + { + id: 'stats', + name: ( + + ), + content: ( + <> + + + +
+ +
+
+ ({ + title, + description: typeof value === 'object' ? JSON.stringify(value) : value, + }))} + /> +
+ + ), + }, + ]; + + return ( + {}} + /> + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 7b66af7b6a484d..72eaae44ea1bfc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -16,6 +16,7 @@ import { EuiButton, EuiSearchBarProps, EuiSpacer, + EuiButtonIcon, } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; @@ -32,6 +33,7 @@ import { ModelsTableToConfigMapping } from './index'; import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; import { DeleteModelsModal } from './delete_models_modal'; import { useNotifications } from '../../../../../contexts/kibana'; +import { ExpandedRow } from './expanded_row'; type Stats = Omit; @@ -59,6 +61,8 @@ export const ModelsList: FC = () => { const [modelsToDelete, setModelsToDelete] = useState([]); + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); + async function fetchData() { setIsLoading(true); try { @@ -158,7 +162,9 @@ export const ModelsList: FC = () => { name: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', { defaultMessage: 'View training data', }), - description: 'Clone this person', + description: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', { + defaultMessage: 'View training data', + }), icon: 'list', type: 'icon', href: 'temp', @@ -181,7 +187,39 @@ export const ModelsList: FC = () => { }, ]; + const toggleDetails = async (item: ModelItem) => { + await fetchModelsStats([item]); + + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[item.model_id]) { + delete itemIdToExpandedRowMapValues[item.model_id]; + } else { + itemIdToExpandedRowMapValues[item.model_id] = ; + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }; + const columns: Array> = [ + { + align: 'left', + width: '40px', + isExpander: true, + render: (item: ModelItem) => ( + toggleDetails(item)} + aria-label={ + itemIdToExpandedRowMap[item.model_id] + ? i18n.translate('xpack.ml.inference.modelsList.collapseRow', { + defaultMessage: 'Collapse', + }) + : i18n.translate('xpack.ml.inference.modelsList.expandRow', { + defaultMessage: 'Expand', + }) + } + iconType={itemIdToExpandedRowMap[item.model_id] ? 'arrowUp' : 'arrowDown'} + /> + ), + }, { field: ModelsTableToConfigMapping.id, name: i18n.translate('xpack.ml.inference.modelsList.modelIdHeader', { @@ -320,6 +358,7 @@ export const ModelsList: FC = () => { rowProps={(item) => ({ 'data-test-subj': `mlModelsTableRow row-${item.model_id}`, })} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} /> {modelsToDelete.length > 0 && ( diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index 59af898e640be1..c044e73355a1a7 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -39,6 +39,7 @@ export interface InferenceConfigResponse { model_id: string; tags: string; version: string; + inference_config: any; }>; } From 96e69199be6b332df0921bd9f3f6550dc55cd164 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 13 Aug 2020 18:53:40 +0200 Subject: [PATCH 08/35] [ML] fix types --- .../models_management/expanded_row.tsx | 79 +++++++++++-------- .../components/models_management/index.ts | 2 +- .../models_management/models_list.tsx | 64 ++++++++++++++- .../services/ml_api_service/inference.ts | 5 +- 4 files changed, 112 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 9dcd07bd4fa17a..9748c75529486e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -6,7 +6,14 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiDescriptionList, EuiPanel, EuiSpacer, EuiTabbedContent, EuiTitle } from '@elastic/eui'; +import { + EuiDescriptionList, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTitle, + EuiNotificationBadge, +} from '@elastic/eui'; import { ModelWithStats } from './models_list'; interface ExpandedRowProps { @@ -38,6 +45,7 @@ export const ExpandedRow: FC = ({ item }) => { />

+ ({ @@ -69,6 +77,7 @@ export const ExpandedRow: FC = ({ item }) => { /> + ({ @@ -80,37 +89,45 @@ export const ExpandedRow: FC = ({ item }) => { ), }, - { - id: 'stats', - name: ( - - ), - content: ( - <> - - - -
+ ...(stats.pipeline_count > 0 + ? [ + { + id: 'pipelines', + name: ( + <> -
-
- ({ - title, - description: typeof value === 'object' ? JSON.stringify(value) : value, - }))} - /> -
- - ), - }, + id="xpack.ml.inference.modelsList.expandedRow.pipelinesTabLabel" + defaultMessage="Pipelines" + />{' '} + {stats.pipeline_count} + + ), + content: ( + <> + + + +
+ +
+
+ + ({ + title, + description: typeof value === 'object' ? JSON.stringify(value) : value, + }))} + /> +
+ + ), + }, + ] + : []), ]; return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts index 918da429203749..7c70a25071640c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts @@ -9,5 +9,5 @@ export * from './models_list'; export enum ModelsTableToConfigMapping { id = 'model_id', createdAt = 'create_time', - type = 'metadata.analysis_config.type', + type = 'type', } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 72eaae44ea1bfc..06268ef96477de 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState, useCallback } from 'react'; +import React, { FC, useEffect, useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -17,6 +17,7 @@ import { EuiSearchBarProps, EuiSpacer, EuiButtonIcon, + EuiBadge, } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; @@ -32,18 +33,25 @@ import { import { ModelsTableToConfigMapping } from './index'; import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; import { DeleteModelsModal } from './delete_models_modal'; -import { useNotifications } from '../../../../../contexts/kibana'; +import { useMlKibana, useNotifications } from '../../../../../contexts/kibana'; import { ExpandedRow } from './expanded_row'; +import { getResultsUrl } from '../analytics_list/common'; type Stats = Omit; export type ModelItem = ModelConfigResponse & { + type?: string; stats?: Stats; }; export type ModelWithStats = Omit & { stats: Stats }; export const ModelsList: FC = () => { + const { + services: { + application: { navigateToUrl }, + }, + } = useMlKibana(); const inferenceApiService = useInferenceApiService(); const { toasts } = useNotifications(); @@ -67,7 +75,14 @@ export const ModelsList: FC = () => { setIsLoading(true); try { const response = await inferenceApiService.getInferenceModel(); - setItems(response.trained_model_configs); + setItems( + response.trained_model_configs.map((v) => ({ + ...v, + ...(typeof v.inference_config === 'object' + ? { type: Object.keys(v.inference_config)[0] } + : {}), + })) + ); setModelsStats({ total: { @@ -125,6 +140,23 @@ export const ModelsList: FC = () => { [items] ); + /** + * Unique inference types from models + */ + const inferenceTypesOptions = useMemo(() => { + const result = items.reduce((acc, item) => { + const type = item.inference_config && Object.keys(item.inference_config)[0]; + if (type) { + acc.add(type); + } + return acc; + }, new Set()); + return [...result].map((v) => ({ + value: v, + name: v, + })); + }, [items]); + async function prepareModelsForDeletion(models: ModelItem[]) { // Fetch model stats to check associated pipelines if (await fetchModelsStats(models)) { @@ -167,7 +199,15 @@ export const ModelsList: FC = () => { }), icon: 'list', type: 'icon', - href: 'temp', + available: (item) => item.metadata?.analytics_config?.id, + onClick: async (item) => { + await navigateToUrl( + getResultsUrl( + item.metadata?.analytics_config.id, + Object.keys(item.metadata?.analytics_config.analysis)[0] + ) + ); + }, isPrimary: true, }, { @@ -235,6 +275,7 @@ export const ModelsList: FC = () => { }), sortable: true, align: 'left', + render: (type: string) => {type}, }, { field: ModelsTableToConfigMapping.createdAt, @@ -279,6 +320,21 @@ export const ModelsList: FC = () => { box: { incremental: true, }, + ...(inferenceTypesOptions && inferenceTypesOptions.length > 0 + ? { + filters: [ + { + type: 'field_value_selection', + field: 'type', + name: i18n.translate('xpack.ml.dataframe.analyticsList.typeFilter', { + defaultMessage: 'Type', + }), + multiSelect: 'or', + options: inferenceTypesOptions, + }, + ], + } + : {}), ...(selectedModels.length > 0 ? { toolsLeft: ( diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index c044e73355a1a7..4aa7ed0ebd164a 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -8,6 +8,7 @@ import { useMemo } from 'react'; import { HttpService } from '../http_service'; import { basePath } from './index'; import { useMlKibana } from '../../contexts/kibana'; +import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common/analytics'; export interface InferenceQueryParams { decompress_definition?: boolean; @@ -30,9 +31,9 @@ export interface InferenceConfigResponse { estimated_heap_memory_usage_bytes: number; estimated_operations: number; license_level: string; - metadata: + metadata?: | { - analysis_config: any; + analytics_config: DataFrameAnalyticsConfig; input: any; } | Record; From 976b46a3873a33e6cf8c011da53330b877d1a25c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 13 Aug 2020 19:12:30 +0200 Subject: [PATCH 09/35] [ML] expanded row --- .../models_management/expanded_row.tsx | 96 ++++++++++++++++++- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 9748c75529486e..364ac36713754d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -13,6 +13,8 @@ import { EuiTabbedContent, EuiTitle, EuiNotificationBadge, + EuiFlexGrid, + EuiFlexItem, } from '@elastic/eui'; import { ModelWithStats } from './models_list'; @@ -21,8 +23,30 @@ interface ExpandedRowProps { } export const ExpandedRow: FC = ({ item }) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { model_id, create_time, created_by, inference_config, stats, ...details } = item; + const { + inference_config: inferenceConfig, + stats, + metadata, + tags, + version, + // eslint-disable-next-line @typescript-eslint/naming-convention + estimated_operations, + // eslint-disable-next-line @typescript-eslint/naming-convention + estimated_heap_memory_usage_bytes, + // eslint-disable-next-line @typescript-eslint/naming-convention + default_field_map, + // eslint-disable-next-line @typescript-eslint/naming-convention + license_level, + } = item; + + const details = { + tags, + version, + estimated_operations, + estimated_heap_memory_usage_bytes, + default_field_map, + license_level, + }; const tabs = [ { @@ -65,6 +89,68 @@ export const ExpandedRow: FC = ({ item }) => { defaultMessage="Config" /> ), + content: ( + <> + + + + + +
+ +
+
+ + ({ + title, + description: + typeof value === 'object' ? JSON.stringify(value) : (value as any), + }) + )} + /> +
+
+ {metadata?.analytics_config && ( + + + +
+ +
+
+ + ({ + title, + description: + typeof value === 'object' ? JSON.stringify(value) : (value as any), + }))} + /> +
+
+ )} +
+ + ), + }, + { + id: 'stats', + name: ( + + ), content: ( <> @@ -72,15 +158,15 @@ export const ExpandedRow: FC = ({ item }) => {
({ + listItems={Object.entries(stats.inference_stats).map(([title, value]) => ({ title, description: typeof value === 'object' ? JSON.stringify(value) : value, }))} From 043f4f2312c773a7786b48647d688bb31f1e0870 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 13 Aug 2020 19:36:54 +0200 Subject: [PATCH 10/35] [ML] fix types --- .../models_management/expanded_row.tsx | 11 ++------ .../services/ml_api_service/inference.ts | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 364ac36713754d..0b99516df0ac91 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -195,19 +195,12 @@ export const ExpandedRow: FC = ({ item }) => {
- ({ - title, - description: typeof value === 'object' ? JSON.stringify(value) : value, - }))} - /> ), diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index 4aa7ed0ebd164a..92807105abf45e 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -46,6 +46,13 @@ export interface InferenceConfigResponse { export type ModelConfigResponse = InferenceConfigResponse['trained_model_configs'][number]; +export interface IngestStats { + count: number; + time_in_millis: number; + current: number; + failed: number; +} + export interface InferenceStatsResponse { count: number; trained_model_stats: Array<{ @@ -58,6 +65,24 @@ export interface InferenceStatsResponse { missing_all_fields_count: number; timestamp: number; }; + ingest: { + total: IngestStats; + pipelines: Record< + string, + IngestStats & { + processors: Array< + Record< + string, + { + // TODO use type from ingest_pipelines + type: string; + stats: IngestStats; + } + > + >; + } + >; + }; }>; } From 4c04e6b946dbcca35bd9ea89258e0b3dd1ba384f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 10:11:03 +0200 Subject: [PATCH 11/35] [ML] fix i18n id --- .../components/models_management/delete_models_modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index fbcfd36f17c768..24e9b253299ed2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -45,7 +45,7 @@ export const DeleteModelsModal: FC = ({ models, onClose From 5c4dcc4480f9c6793bbf70f6e7e170f9f91a7273 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 10:20:31 +0200 Subject: [PATCH 12/35] [ML] change server-side permission check --- x-pack/plugins/ml/server/routes/inference.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index 0261a29dfa9b42..4ce7170c31df18 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -23,7 +23,7 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { params: optionalModelIdSchema, }, options: { - tags: ['access:ml:canAccessML'], + tags: ['access:ml:canGetDataFrameAnalytics'], }, }, mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { @@ -55,7 +55,7 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { params: modelIdSchema, }, options: { - tags: ['access:ml:canAccessML'], + tags: ['access:ml:canGetDataFrameAnalytics'], }, }, mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { From d6123a31ee9f8b4e77c38694b3480d6debeab579 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 11:04:31 +0200 Subject: [PATCH 13/35] [ML] refactor types --- .../ml/common/types/data_frame_analytics.ts | 70 ++++++++++++++++++ x-pack/plugins/ml/common/types/inference.ts | 63 ++++++++++++++++ .../data_frame_analytics/common/analytics.ts | 74 ++----------------- .../data_frame_analytics/common/fields.ts | 2 +- .../common/get_index_data.ts | 3 +- .../data_frame_analytics/common/index.ts | 6 +- .../pages/analytics_creation/page.tsx | 2 +- .../models_management/models_list.tsx | 9 +-- .../hooks/use_create_analytics_form/state.ts | 9 +-- .../services/ml_api_service/inference.ts | 55 +------------- .../ml/data_frame_analytics_creation.ts | 8 +- 11 files changed, 162 insertions(+), 139 deletions(-) create mode 100644 x-pack/plugins/ml/common/types/inference.ts diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index 5ba7f9c191a7fe..f0aac750475857 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -5,7 +5,77 @@ */ import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; + export interface DeleteDataFrameAnalyticsWithIndexStatus { success: boolean; error?: CustomHttpResponseOptions; } + +export type IndexName = string; +export type DataFrameAnalyticsId = string; + +export interface OutlierAnalysis { + [key: string]: {}; + + outlier_detection: {}; +} + +interface Regression { + dependent_variable: string; + training_percent?: number; + num_top_feature_importance_values?: number; + prediction_field_name?: string; +} + +interface Classification { + dependent_variable: string; + training_percent?: number; + num_top_classes?: string; + num_top_feature_importance_values?: number; + prediction_field_name?: string; +} + +export interface RegressionAnalysis { + [key: string]: Regression; + + regression: Regression; +} + +export interface ClassificationAnalysis { + [key: string]: Classification; + + classification: Classification; +} + +interface GenericAnalysis { + [key: string]: Record; +} + +export type AnalysisConfig = + | OutlierAnalysis + | RegressionAnalysis + | ClassificationAnalysis + | GenericAnalysis; + +export interface DataFrameAnalyticsConfig { + id: DataFrameAnalyticsId; + description?: string; + dest: { + index: IndexName; + results_field: string; + }; + source: { + index: IndexName | IndexName[]; + query?: any; + }; + analysis: AnalysisConfig; + analyzed_fields: { + includes: string[]; + excludes: string[]; + }; + model_memory_limit: string; + max_num_threads?: number; + create_time: number; + version: string; + allow_lazy_start?: boolean; +} diff --git a/x-pack/plugins/ml/common/types/inference.ts b/x-pack/plugins/ml/common/types/inference.ts new file mode 100644 index 00000000000000..18214836d92a4b --- /dev/null +++ b/x-pack/plugins/ml/common/types/inference.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DataFrameAnalyticsConfig } from './data_frame_analytics'; + +export interface IngestStats { + count: number; + time_in_millis: number; + current: number; + failed: number; +} + +export interface TrainedModelStat { + model_id: string; + pipeline_count: number; + inference_stats: { + failure_count: number; + inference_count: number; + cache_miss_count: number; + missing_all_fields_count: number; + timestamp: number; + }; + ingest: { + total: IngestStats; + pipelines: Record< + string, + IngestStats & { + processors: Array< + Record< + string, + { + // TODO use type from ingest_pipelines plugin + type: string; + stats: IngestStats; + } + > + >; + } + >; + }; +} + +export interface ModelConfigResponse { + created_by: string; + create_time: string; + default_field_map: Record; + estimated_heap_memory_usage_bytes: number; + estimated_operations: number; + license_level: string; + metadata?: + | { + analytics_config: DataFrameAnalyticsConfig; + input: any; + } + | Record; + model_id: string; + tags: string; + version: string; + inference_config: any; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index 561e8642772f05..88d917243f8665 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -5,18 +5,21 @@ */ import { useEffect } from 'react'; -import { BehaviorSubject } from 'rxjs'; -import { filter, distinctUntilChanged } from 'rxjs/operators'; -import { Subscription } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter } from 'rxjs/operators'; import { cloneDeep } from 'lodash'; import { ml } from '../../services/ml_api_service'; import { Dictionary } from '../../../../common/types/common'; import { getErrorMessage } from '../../../../common/util/errors'; import { SavedSearchQuery } from '../../contexts/ml'; +import { + AnalysisConfig, + ClassificationAnalysis, + OutlierAnalysis, + RegressionAnalysis, +} from '../../../../common/types/data_frame_analytics'; -export type IndexName = string; export type IndexPattern = string; -export type DataFrameAnalyticsId = string; export enum ANALYSIS_CONFIG_TYPE { OUTLIER_DETECTION = 'outlier_detection', @@ -46,34 +49,6 @@ export enum OUTLIER_ANALYSIS_METHOD { DISTANCE_KNN = 'distance_knn', } -interface OutlierAnalysis { - [key: string]: {}; - outlier_detection: {}; -} - -interface Regression { - dependent_variable: string; - training_percent?: number; - num_top_feature_importance_values?: number; - prediction_field_name?: string; -} -export interface RegressionAnalysis { - [key: string]: Regression; - regression: Regression; -} - -interface Classification { - dependent_variable: string; - training_percent?: number; - num_top_classes?: string; - num_top_feature_importance_values?: number; - prediction_field_name?: string; -} -export interface ClassificationAnalysis { - [key: string]: Classification; - classification: Classification; -} - export interface LoadExploreDataArg { filterByIsTraining?: boolean; searchQuery: SavedSearchQuery; @@ -165,22 +140,12 @@ export interface ClassificationEvaluateResponse { }; } -interface GenericAnalysis { - [key: string]: Record; -} - interface LoadEvaluateResult { success: boolean; eval: RegressionEvaluateResponse | ClassificationEvaluateResponse | null; error: string | null; } -type AnalysisConfig = - | OutlierAnalysis - | RegressionAnalysis - | ClassificationAnalysis - | GenericAnalysis; - export const getAnalysisType = (analysis: AnalysisConfig): string => { const keys = Object.keys(analysis); @@ -342,29 +307,6 @@ export interface UpdateDataFrameAnalyticsConfig { max_num_threads?: number; } -export interface DataFrameAnalyticsConfig { - id: DataFrameAnalyticsId; - description?: string; - dest: { - index: IndexName; - results_field: string; - }; - source: { - index: IndexName | IndexName[]; - query?: any; - }; - analysis: AnalysisConfig; - analyzed_fields: { - includes: string[]; - excludes: string[]; - }; - model_memory_limit: string; - max_num_threads?: number; - create_time: number; - version: string; - allow_lazy_start?: boolean; -} - export enum REFRESH_ANALYTICS_LIST_STATE { ERROR = 'error', IDLE = 'idle', diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index 1b99aac812fcdd..847aefefbc6c87 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -13,13 +13,13 @@ import { isClassificationAnalysis, isOutlierAnalysis, isRegressionAnalysis, - DataFrameAnalyticsConfig, } from './analytics'; import { Field } from '../../../../common/types/fields'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; import { newJobCapsService } from '../../services/new_job_capabilities_service'; import { FEATURE_IMPORTANCE, FEATURE_INFLUENCE, OUTLIER_SCORE, TOP_CLASSES } from './constants'; +import { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; export type EsId = string; export type EsDocSource = Record; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts index eb38a23d10eef1..c162cb2754c102 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -12,7 +12,8 @@ import { ml } from '../../services/ml_api_service'; import { isKeywordAndTextType } from '../common/fields'; import { SavedSearchQuery } from '../../contexts/ml'; -import { DataFrameAnalyticsConfig, INDEX_STATUS } from './analytics'; +import { INDEX_STATUS } from './analytics'; +import { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; export const getIndexData = async ( jobConfig: DataFrameAnalyticsConfig | undefined, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts index 65531009e4436b..00d735d9a866e9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts @@ -11,10 +11,7 @@ export { isOutlierAnalysis, refreshAnalyticsList$, useRefreshAnalyticsList, - DataFrameAnalyticsId, - DataFrameAnalyticsConfig, UpdateDataFrameAnalyticsConfig, - IndexName, IndexPattern, REFRESH_ANALYTICS_LIST_STATE, ANALYSIS_CONFIG_TYPE, @@ -45,3 +42,6 @@ export { getIndexData } from './get_index_data'; export { getIndexFields } from './get_index_fields'; export { useResultsViewConfig } from './use_results_view_config'; +export { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; +export { DataFrameAnalyticsId } from '../../../../common/types/data_frame_analytics'; +export { IndexName } from '../../../../common/types/data_frame_analytics'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx index 2f0e2ed3428c0f..da5caf8e3875a5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx @@ -23,10 +23,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useMlContext } from '../../../contexts/ml'; import { newJobCapsService } from '../../../services/new_job_capabilities_service'; import { ml } from '../../../services/ml_api_service'; -import { DataFrameAnalyticsId } from '../../common/analytics'; import { useCreateAnalyticsForm } from '../analytics_management/hooks/use_create_analytics_form'; import { CreateAnalyticsAdvancedEditor } from './components/create_analytics_advanced_editor'; import { AdvancedStep, ConfigurationStep, CreateStep, DetailsStep } from './components'; +import { DataFrameAnalyticsId } from '../../../../../common/types/data_frame_analytics'; export enum ANALYTICS_STEPS { CONFIGURATION, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 06268ef96477de..6ad16daf0a9fb8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -25,19 +25,16 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; import { Action } from '@elastic/eui/src/components/basic_table/action_types'; import { StatsBar, ModelsBarStats } from '../../../../../components/stats_bar'; -import { - ModelConfigResponse, - ModelStats, - useInferenceApiService, -} from '../../../../../services/ml_api_service/inference'; +import { useInferenceApiService } from '../../../../../services/ml_api_service/inference'; import { ModelsTableToConfigMapping } from './index'; import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; import { DeleteModelsModal } from './delete_models_modal'; import { useMlKibana, useNotifications } from '../../../../../contexts/kibana'; import { ExpandedRow } from './expanded_row'; import { getResultsUrl } from '../analytics_list/common'; +import { ModelConfigResponse, TrainedModelStat } from '../../../../../../../common/types/inference'; -type Stats = Omit; +type Stats = Omit; export type ModelItem = ModelConfigResponse & { type?: string; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index f932e4d0db7d7e..4926decaa7f9c0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -8,13 +8,12 @@ import { DeepPartial, DeepReadonly } from '../../../../../../../common/types/com import { checkPermission } from '../../../../../capabilities/check_capabilities'; import { mlNodesAvailable } from '../../../../../ml_nodes_check'; +import { ANALYSIS_CONFIG_TYPE, defaultSearchQuery } from '../../../../common/analytics'; +import { CloneDataFrameAnalyticsConfig } from '../../components/action_clone'; import { - DataFrameAnalyticsId, DataFrameAnalyticsConfig, - ANALYSIS_CONFIG_TYPE, - defaultSearchQuery, -} from '../../../../common/analytics'; -import { CloneDataFrameAnalyticsConfig } from '../../components/action_clone'; + DataFrameAnalyticsId, +} from '../../../../../../../common/types/data_frame_analytics'; export enum DEFAULT_MODEL_MEMORY_LIMIT { regression = '100mb', diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index 92807105abf45e..be83a583d0f329 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -8,7 +8,7 @@ import { useMemo } from 'react'; import { HttpService } from '../http_service'; import { basePath } from './index'; import { useMlKibana } from '../../contexts/kibana'; -import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common/analytics'; +import { ModelConfigResponse, TrainedModelStat } from '../../../../common/types/inference'; export interface InferenceQueryParams { decompress_definition?: boolean; @@ -24,28 +24,9 @@ export interface InferenceStatsQueryParams { } export interface InferenceConfigResponse { - trained_model_configs: Array<{ - created_by: string; - create_time: string; - default_field_map: Record; - estimated_heap_memory_usage_bytes: number; - estimated_operations: number; - license_level: string; - metadata?: - | { - analytics_config: DataFrameAnalyticsConfig; - input: any; - } - | Record; - model_id: string; - tags: string; - version: string; - inference_config: any; - }>; + trained_model_configs: ModelConfigResponse[]; } -export type ModelConfigResponse = InferenceConfigResponse['trained_model_configs'][number]; - export interface IngestStats { count: number; time_in_millis: number; @@ -55,39 +36,9 @@ export interface IngestStats { export interface InferenceStatsResponse { count: number; - trained_model_stats: Array<{ - model_id: string; - pipeline_count: number; - inference_stats: { - failure_count: number; - inference_count: number; - cache_miss_count: number; - missing_all_fields_count: number; - timestamp: number; - }; - ingest: { - total: IngestStats; - pipelines: Record< - string, - IngestStats & { - processors: Array< - Record< - string, - { - // TODO use type from ingest_pipelines - type: string; - stats: IngestStats; - } - > - >; - } - >; - }; - }>; + trained_model_stats: TrainedModelStat[]; } -export type ModelStats = InferenceStatsResponse['trained_model_stats'][number]; - /** * Service with APIs calls to perform inference operations. * @param httpService diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index 474d6845ef3ec3..cdd26b60d3be09 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -5,14 +5,14 @@ */ import expect from '@kbn/expect'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; -import { - ClassificationAnalysis, - RegressionAnalysis, -} from '../../../../plugins/ml/public/application/data_frame_analytics/common/analytics'; import { FtrProviderContext } from '../../ftr_provider_context'; import { MlCommon } from './common'; import { MlApi } from './api'; +import { + ClassificationAnalysis, + RegressionAnalysis, +} from '../../../../plugins/ml/common/types/data_frame_analytics'; enum ANALYSIS_CONFIG_TYPE { OUTLIER_DETECTION = 'outlier_detection', From 4ce85fb9384e24c357e3046004526b8ff209c99f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 11:37:19 +0200 Subject: [PATCH 14/35] [ML] show success toast on model deletion, fix models counter --- .../models_management/models_list.tsx | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 6ad16daf0a9fb8..0e17679bb42aa9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -60,7 +60,6 @@ export const ModelsList: FC = () => { const [sortDirection, setSortDirection] = useState('asc'); const [isLoading, setIsLoading] = useState(false); - const [modelsStats, setModelsStats] = useState(); const [items, setItems] = useState([]); const [selectedModels, setSelectedModels] = useState([]); @@ -68,6 +67,9 @@ export const ModelsList: FC = () => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); + /** + * Fetches inference trained models. + */ async function fetchData() { setIsLoading(true); try { @@ -80,16 +82,6 @@ export const ModelsList: FC = () => { : {}), })) ); - - setModelsStats({ - total: { - show: true, - value: response.trained_model_configs.length, - label: i18n.translate('xpack.ml.inference.modelsList.totalAmountLabel', { - defaultMessage: 'Total inference trained models', - }), - }, - }); } catch (error) { toasts.addError(new Error(error.body.message), { title: i18n.translate('xpack.ml.inference.modelsList.fetchFailedErrorMessage', { @@ -100,6 +92,18 @@ export const ModelsList: FC = () => { setIsLoading(false); } + const modelsStats: ModelsBarStats = useMemo(() => { + return { + total: { + show: true, + value: items.length, + label: i18n.translate('xpack.ml.inference.modelsList.totalAmountLabel', { + defaultMessage: 'Total inference trained models', + }), + }, + }; + }, [items]); + useEffect(() => { fetchData(); }, []); @@ -167,20 +171,38 @@ export const ModelsList: FC = () => { } } + /** + * Deletes the models marked for deletion. + */ async function deleteModels() { + const modelsToDeleteIds = modelsToDelete.map((model) => model.model_id); + try { await Promise.all( - modelsToDelete.map((model) => inferenceApiService.deleteInferenceModel(model.model_id)) + modelsToDeleteIds.map((modelId) => inferenceApiService.deleteInferenceModel(modelId)) ); setItems( items.filter( (model) => !modelsToDelete.some((toDelete) => toDelete.model_id === model.model_id) ) ); + toasts.addSuccess( + i18n.translate('xpack.ml.inference.modelsList.successfullyDeletedMessage', { + defaultMessage: + '{modelsCount, plural, one {Model} other {Models}} {modelsToDeleteIds} {modelsCount, plural, one {has} other {have}} been successfully deleted', + values: { + modelsCount: modelsToDeleteIds.length, + modelsToDeleteIds: modelsToDeleteIds.join(', '), + }, + }) + ); } catch (error) { - toasts.addError(new Error(error.body.message), { + toasts.addError(new Error(error?.body?.message), { title: i18n.translate('xpack.ml.inference.modelsList.fetchDeletionErrorMessage', { - defaultMessage: 'Model deletion failed', + defaultMessage: '{modelsCount, plural, one {Model} other {Models}} deletion failed', + values: { + modelsCount: modelsToDeleteIds.length, + }, }), }); } From 41a8a10da710748c685723d43d39011792d6642c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 12:17:57 +0200 Subject: [PATCH 15/35] [ML] update expanded row --- x-pack/plugins/ml/common/types/inference.ts | 2 +- .../models_management/expanded_row.tsx | 176 ++++++++++-------- 2 files changed, 96 insertions(+), 82 deletions(-) diff --git a/x-pack/plugins/ml/common/types/inference.ts b/x-pack/plugins/ml/common/types/inference.ts index 18214836d92a4b..9d4e6eeb474c63 100644 --- a/x-pack/plugins/ml/common/types/inference.ts +++ b/x-pack/plugins/ml/common/types/inference.ts @@ -59,5 +59,5 @@ export interface ModelConfigResponse { model_id: string; tags: string; version: string; - inference_config: any; + inference_config?: Record; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 0b99516df0ac91..6793a1f434a193 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -48,6 +48,17 @@ export const ExpandedRow: FC = ({ item }) => { license_level, }; + function formatToListItems(items: Record) { + return Object.entries(items) + .map(([title, value]) => ({ + title, + description: typeof value === 'object' ? JSON.stringify(value) : value, + })) + .filter(({ description }) => { + return description !== undefined; + }); + } + const tabs = [ { id: 'details', @@ -57,38 +68,6 @@ export const ExpandedRow: FC = ({ item }) => { defaultMessage="Details" /> ), - content: ( - <> - - - -
- -
-
- - ({ - title, - description: typeof value === 'object' ? JSON.stringify(value) : value, - }))} - /> -
- - ), - }, - { - id: 'config', - name: ( - - ), content: ( <> @@ -98,51 +77,84 @@ export const ExpandedRow: FC = ({ item }) => {
({ - title, - description: - typeof value === 'object' ? JSON.stringify(value) : (value as any), - }) - )} + listItems={formatToListItems(details)} />
- {metadata?.analytics_config && ( - - - -
- -
-
- - ({ - title, - description: - typeof value === 'object' ? JSON.stringify(value) : (value as any), - }))} - /> -
-
- )} ), }, + ...(inferenceConfig + ? [ + { + id: 'config', + name: ( + + ), + content: ( + <> + + + + + +
+ +
+
+ + +
+
+ {metadata?.analytics_config && ( + + + +
+ +
+
+ + +
+
+ )} +
+ + ), + }, + ] + : []), + { id: 'stats', name: ( @@ -154,24 +166,26 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - - -
- + + + +
+ +
+
+ + -
-
- - ({ - title, - description: typeof value === 'object' ? JSON.stringify(value) : value, - }))} - /> -
+ + + ), }, From 77ba46389757b94f8b7db1b156b25969f867f5b5 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 14:04:25 +0200 Subject: [PATCH 16/35] [ML] pipelines stats --- x-pack/plugins/ml/common/types/inference.ts | 4 +- .../models_management/expanded_row.tsx | 120 +++++++++++++----- 2 files changed, 91 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/ml/common/types/inference.ts b/x-pack/plugins/ml/common/types/inference.ts index 9d4e6eeb474c63..aabe5c90a2b3a9 100644 --- a/x-pack/plugins/ml/common/types/inference.ts +++ b/x-pack/plugins/ml/common/types/inference.ts @@ -16,14 +16,14 @@ export interface IngestStats { export interface TrainedModelStat { model_id: string; pipeline_count: number; - inference_stats: { + inference_stats?: { failure_count: number; inference_count: number; cache_miss_count: number; missing_all_fields_count: number; timestamp: number; }; - ingest: { + ingest?: { total: IngestStats; pipelines: Record< string, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 6793a1f434a193..7953b437278fa8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -154,7 +154,6 @@ export const ExpandedRow: FC = ({ item }) => { }, ] : []), - { id: 'stats', name: ( @@ -167,29 +166,51 @@ export const ExpandedRow: FC = ({ item }) => { <> - - - -
- -
-
- - -
-
+ {stats.inference_stats && ( + + + +
+ +
+
+ + +
+
+ )} + {stats.ingest?.total && ( + + + +
+ +
+
+ + +
+
+ )}
), }, - ...(stats.pipeline_count > 0 + ...(stats.ingest?.pipelines ? [ { id: 'pipelines', @@ -205,17 +226,54 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - - -
- -
-
- -
+ + {Object.entries(stats.ingest?.pipelines).map( + ([pipelineName, { processors, ...pipelineStats }]) => { + return ( + + + +
{pipelineName}
+
+ + + + +
+ +
+
+ + {processors.map((processor) => { + const name = Object.keys(processor)[0]; + const { stats: processorStats } = processor[name]; + return ( + + +
{name}
+
+ +
+ ); + })} +
+
+
+ ); + } + )} +
), }, From 72da967f5de394be096874f23fcc8a357759f301 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 14:32:48 +0200 Subject: [PATCH 17/35] [ML] use refresh observable --- .../models_management/models_list.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 0e17679bb42aa9..b7354c0316fa33 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState, useCallback, useMemo } from 'react'; +import React, { FC, useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -33,6 +33,11 @@ import { useMlKibana, useNotifications } from '../../../../../contexts/kibana'; import { ExpandedRow } from './expanded_row'; import { getResultsUrl } from '../analytics_list/common'; import { ModelConfigResponse, TrainedModelStat } from '../../../../../../../common/types/inference'; +import { + REFRESH_ANALYTICS_LIST_STATE, + refreshAnalyticsList$, + useRefreshAnalyticsList, +} from '../../../../common'; type Stats = Omit; @@ -67,11 +72,16 @@ export const ModelsList: FC = () => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); + // Subscribe to the refresh observable to trigger reloading the model list. + useRefreshAnalyticsList({ + isLoading: setIsLoading, + onRefresh: () => fetchData(), + }); + /** * Fetches inference trained models. */ async function fetchData() { - setIsLoading(true); try { const response = await inferenceApiService.getInferenceModel(); setItems( @@ -90,6 +100,7 @@ export const ModelsList: FC = () => { }); } setIsLoading(false); + refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE); } const modelsStats: ModelsBarStats = useMemo(() => { @@ -104,10 +115,6 @@ export const ModelsList: FC = () => { }; }, [items]); - useEffect(() => { - fetchData(); - }, []); - /** * Fetches models stats and update the original object */ From 43f320d9832340c637181db0d221d6f398231ba9 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 14 Aug 2020 15:06:09 +0200 Subject: [PATCH 18/35] [ML] endpoint to fetch associated pipelines --- .../services/ml_api_service/inference.ts | 17 ++++++++ x-pack/plugins/ml/server/routes/inference.ts | 41 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index be83a583d0f329..35cae5cb644c7f 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -85,6 +85,23 @@ export function inferenceApiProvider(httpService: HttpService) { }); }, + /** + * Fetches pipelines associated with provided models + * + * @param modelId - Model ID, collection of Model IDs. + */ + getInferenceModelPipelines(modelId: string | string[]) { + let model = modelId; + if (Array.isArray(modelId)) { + model = modelId.join(','); + } + + return httpService.http({ + path: `${apiBasePath}/inference/${model}/pipelines`, + method: 'GET', + }); + }, + /** * Deletes an existing trained inference model. * diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index 4ce7170c31df18..21dfba446745b9 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -73,6 +73,47 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Inference + * + * @api {get} /api/ml/inference/:modelId/pipelines Get model pipelines + * @apiName GetModelPipelines + * @apiDescription Retrieves pipelines associated with a model + */ + router.get( + { + path: '/api/ml/inference/{modelId}/pipelines', + validate: { + params: modelIdSchema, + }, + options: { + tags: ['access:ml:canGetDataFrameAnalytics'], + }, + }, + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { + try { + const { modelId } = request.params; + const modelsIds = modelId.split(','); + + const { body } = await client.asCurrentUser.ingest.getPipeline(); + + const result = Object.entries(body).reduce((acc, [pipelineName, pipelineDefinition]) => { + const { processors } = pipelineDefinition as { processors: Array> }; + if (processors.some((processor) => modelsIds.includes(processor.inference?.model_id))) { + acc[pipelineName] = pipelineDefinition; + } + return acc; + }, {} as Record); + + return response.ok({ + body: result, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + /** * @apiGroup Inference * From 5b12bfd9048d517548c686a561a892087ed6f966 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 09:49:40 +0200 Subject: [PATCH 19/35] [ML] update the endpoint to fetch associated pipelines --- x-pack/plugins/ml/common/types/inference.ts | 5 +++++ .../services/ml_api_service/inference.ts | 8 +++++-- x-pack/plugins/ml/server/routes/inference.ts | 21 ++++++++++++------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/common/types/inference.ts b/x-pack/plugins/ml/common/types/inference.ts index aabe5c90a2b3a9..2c003aa99e1947 100644 --- a/x-pack/plugins/ml/common/types/inference.ts +++ b/x-pack/plugins/ml/common/types/inference.ts @@ -61,3 +61,8 @@ export interface ModelConfigResponse { version: string; inference_config?: Record; } + +export interface ModelPipelines { + model_id: string; + pipelines: Record>; description?: string }>; +} diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index 35cae5cb644c7f..6098c5c7c668ad 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -8,7 +8,11 @@ import { useMemo } from 'react'; import { HttpService } from '../http_service'; import { basePath } from './index'; import { useMlKibana } from '../../contexts/kibana'; -import { ModelConfigResponse, TrainedModelStat } from '../../../../common/types/inference'; +import { + ModelConfigResponse, + ModelPipelines, + TrainedModelStat, +} from '../../../../common/types/inference'; export interface InferenceQueryParams { decompress_definition?: boolean; @@ -96,7 +100,7 @@ export function inferenceApiProvider(httpService: HttpService) { model = modelId.join(','); } - return httpService.http({ + return httpService.http({ path: `${apiBasePath}/inference/${model}/pipelines`, method: 'GET', }); diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index 21dfba446745b9..9ee99b9de1c8ef 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -93,20 +93,27 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { modelId } = request.params; - const modelsIds = modelId.split(','); + + const modelIdsMap = new Map>( + modelId.split(',').map((id: string) => [id, {}]) + ); const { body } = await client.asCurrentUser.ingest.getPipeline(); - const result = Object.entries(body).reduce((acc, [pipelineName, pipelineDefinition]) => { + for (const [pipelineName, pipelineDefinition] of Object.entries(body)) { const { processors } = pipelineDefinition as { processors: Array> }; - if (processors.some((processor) => modelsIds.includes(processor.inference?.model_id))) { - acc[pipelineName] = pipelineDefinition; + + for (const processor of processors) { + const id = processor.inference?.model_id; + const obj = modelIdsMap.get(id); + if (obj) { + obj[pipelineName] = pipelineDefinition; + } } - return acc; - }, {} as Record); + } return response.ok({ - body: result, + body: [...modelIdsMap.entries()].map(([id, pipelines]) => ({ model_id: id, pipelines })), }); } catch (e) { return response.customError(wrapError(e)); From 14f8d7b7d7d41d5069086dd1fee20c57755848fa Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 10:03:08 +0200 Subject: [PATCH 20/35] [ML] show pipelines definition in expanded row --- .../models_management/delete_models_modal.tsx | 4 +- .../models_management/expanded_row.tsx | 137 +++++++++++------- .../models_management/models_list.tsx | 46 ++++-- 3 files changed, 125 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index 24e9b253299ed2..d52571d8f782c0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -18,10 +18,10 @@ import { EuiSpacer, EuiCallOut, } from '@elastic/eui'; -import { ModelWithStats } from './models_list'; +import { ModelItemFull } from './models_list'; interface DeleteModelsModalProps { - models: ModelWithStats[]; + models: ModelItemFull[]; onClose: (deletionApproved?: boolean) => void; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 7953b437278fa8..0867ca9213c3c6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -15,11 +15,13 @@ import { EuiNotificationBadge, EuiFlexGrid, EuiFlexItem, + EuiCodeBlock, + EuiText, } from '@elastic/eui'; -import { ModelWithStats } from './models_list'; +import { ModelItemFull } from './models_list'; interface ExpandedRowProps { - item: ModelWithStats; + item: ModelItemFull; } export const ExpandedRow: FC = ({ item }) => { @@ -37,6 +39,7 @@ export const ExpandedRow: FC = ({ item }) => { default_field_map, // eslint-disable-next-line @typescript-eslint/naming-convention license_level, + pipelines, } = item; const details = { @@ -143,7 +146,7 @@ export const ExpandedRow: FC = ({ item }) => { @@ -207,10 +210,60 @@ export const ExpandedRow: FC = ({ item }) => { )} + {stats.ingest?.pipelines && ( + + {Object.entries(stats.ingest.pipelines).map( + ([pipelineName, { processors, ...pipelineStats }]) => { + return ( + + + +
{pipelineName}
+
+ + + + +
+ +
+
+ + {processors.map((processor) => { + const name = Object.keys(processor)[0]; + const { stats: processorStats } = processor[name]; + return ( + + +
{name}
+
+ +
+ ); + })} +
+
+
+ ); + } + )} +
+ )} ), }, - ...(stats.ingest?.pipelines + ...(pipelines && Object.keys(pipelines).length > 0 ? [ { id: 'pipelines', @@ -227,52 +280,36 @@ export const ExpandedRow: FC = ({ item }) => { <> - {Object.entries(stats.ingest?.pipelines).map( - ([pipelineName, { processors, ...pipelineStats }]) => { - return ( - - - -
{pipelineName}
-
- - - - -
- -
-
- - {processors.map((processor) => { - const name = Object.keys(processor)[0]; - const { stats: processorStats } = processor[name]; - return ( - - -
{name}
-
- -
- ); - })} -
-
-
- ); - } - )} + {Object.entries(pipelines).map(([pipelineName, { processors, description }]) => { + return ( + + + +
{pipelineName}
+
+ {description && {description}} + + +
+ +
+
+ + {JSON.stringify(processors, null, 2)} + +
+
+ ); + })}
), diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index b7354c0316fa33..841d00bdb92f1b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -32,7 +32,11 @@ import { DeleteModelsModal } from './delete_models_modal'; import { useMlKibana, useNotifications } from '../../../../../contexts/kibana'; import { ExpandedRow } from './expanded_row'; import { getResultsUrl } from '../analytics_list/common'; -import { ModelConfigResponse, TrainedModelStat } from '../../../../../../../common/types/inference'; +import { + ModelConfigResponse, + ModelPipelines, + TrainedModelStat, +} from '../../../../../../../common/types/inference'; import { REFRESH_ANALYTICS_LIST_STATE, refreshAnalyticsList$, @@ -44,9 +48,10 @@ type Stats = Omit; export type ModelItem = ModelConfigResponse & { type?: string; stats?: Stats; + pipelines?: ModelPipelines['pipelines']; }; -export type ModelWithStats = Omit & { stats: Stats }; +export type ModelItemFull = Required; export const ModelsList: FC = () => { const { @@ -68,7 +73,7 @@ export const ModelsList: FC = () => { const [items, setItems] = useState([]); const [selectedModels, setSelectedModels] = useState([]); - const [modelsToDelete, setModelsToDelete] = useState([]); + const [modelsToDelete, setModelsToDelete] = useState([]); const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); @@ -115,6 +120,28 @@ export const ModelsList: FC = () => { }; }, [items]); + const fetchAssociatedPipelines = useCallback( + async (models: ModelItem[]) => { + try { + const pipelines = await inferenceApiService.getInferenceModelPipelines( + models.map((model) => model.model_id) + ); + + for (const { model_id: id, pipelines: modelPipelines } of pipelines) { + const model = models.find((m) => m.model_id === id); + model!.pipelines = modelPipelines; + } + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.inference.modelsList.fetchModelPipelinesErrorMessage', { + defaultMessage: 'Failed to fetch associated pipelines', + }), + }); + } + }, + [items] + ); + /** * Fetches models stats and update the original object */ @@ -135,7 +162,6 @@ export const ModelsList: FC = () => { const model = models.find((m) => m.model_id === id); model!.stats = stats; } - setItems([...items]); return true; } catch (error) { toasts.addError(new Error(error.body.message), { @@ -168,7 +194,7 @@ export const ModelsList: FC = () => { async function prepareModelsForDeletion(models: ModelItem[]) { // Fetch model stats to check associated pipelines if (await fetchModelsStats(models)) { - setModelsToDelete(models as ModelWithStats[]); + setModelsToDelete(models as ModelItemFull[]); } else { toasts.addDanger( i18n.translate('xpack.ml.inference.modelsList.unableToDeleteModelsErrorMessage', { @@ -254,13 +280,13 @@ export const ModelsList: FC = () => { ]; const toggleDetails = async (item: ModelItem) => { - await fetchModelsStats([item]); - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; if (itemIdToExpandedRowMapValues[item.model_id]) { delete itemIdToExpandedRowMapValues[item.model_id]; } else { - itemIdToExpandedRowMapValues[item.model_id] = ; + await fetchModelsStats([item]); + await fetchAssociatedPipelines([item]); + itemIdToExpandedRowMapValues[item.model_id] = ; } setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); }; @@ -272,7 +298,7 @@ export const ModelsList: FC = () => { isExpander: true, render: (item: ModelItem) => ( toggleDetails(item)} + onClick={toggleDetails.bind(null, item)} aria-label={ itemIdToExpandedRowMap[item.model_id] ? i18n.translate('xpack.ml.inference.modelsList.collapseRow', { @@ -428,6 +454,7 @@ export const ModelsList: FC = () => { columns={columns} hasActions={true} isExpandable={true} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} isSelectable={false} items={items} itemId={ModelsTableToConfigMapping.id} @@ -440,7 +467,6 @@ export const ModelsList: FC = () => { rowProps={(item) => ({ 'data-test-subj': `mlModelsTableRow row-${item.model_id}`, })} - itemIdToExpandedRowMap={itemIdToExpandedRowMap} /> {modelsToDelete.length > 0 && ( From 3e6f2ec7b804140c545d5fb51db0ab96eb615350 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 11:31:39 +0200 Subject: [PATCH 21/35] [ML] change stats layout --- .../models_management/expanded_row.tsx | 177 ++++++++++-------- 1 file changed, 99 insertions(+), 78 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 0867ca9213c3c6..bb3fd67b55533a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { FC, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescriptionList, @@ -17,6 +17,8 @@ import { EuiFlexItem, EuiCodeBlock, EuiText, + EuiFlexGroup, + EuiStat, } from '@elastic/eui'; import { ModelItemFull } from './models_list'; @@ -74,7 +76,7 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - + @@ -110,7 +112,7 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - + @@ -168,97 +170,116 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - - {stats.inference_stats && ( - - - -
- -
-
- - + + +
+ +
+
+ + + {formatToListItems(stats.inference_stats).map(({ title, description }) => ( + + + + ))} + +
+ + + )} + {stats.ingest?.total && ( + + +
+ - - - )} - {stats.ingest?.total && ( - - +
+
+ + + {formatToListItems(stats.ingest.total).map(({ title, description }) => ( + + + + ))} + + + {stats.ingest?.pipelines && ( + <> +
- - -
-
- )} -
- {stats.ingest?.pipelines && ( - - {Object.entries(stats.ingest.pipelines).map( - ([pipelineName, { processors, ...pipelineStats }]) => { - return ( - - - -
{pipelineName}
-
- - - - -
- -
-
- + {Object.entries(stats.ingest.pipelines).map( + ([pipelineName, { processors, ...pipelineStats }]) => { + return ( + + +
{pipelineName}
+
+ + + {formatToListItems(pipelineStats).map(({ title, description }) => ( + + + + ))} + + + +
+ +
+
{processors.map((processor) => { const name = Object.keys(processor)[0]; const { stats: processorStats } = processor[name]; return ( - +
{name}
- -
+ + {formatToListItems(processorStats).map( + ({ title, description }) => ( + + + + ) + )} + + +
); })} -
-
-
- ); - } + +
+ ); + } + )} + )} - + )} ), @@ -279,7 +300,7 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - + {Object.entries(pipelines).map(([pipelineName, { processors, description }]) => { return ( From 36182a078eb9ce11ddf4be63639fd4e363c9af36 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 11:33:49 +0200 Subject: [PATCH 22/35] [ML] fix headers --- .../components/models_management/expanded_row.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index bb3fd67b55533a..4fa8ed505904d8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -177,7 +177,7 @@ export const ExpandedRow: FC = ({ item }) => {
@@ -199,7 +199,7 @@ export const ExpandedRow: FC = ({ item }) => {
From 4eab787c8473276fc1a46f5f48b2b765a3532ee9 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 11:45:40 +0200 Subject: [PATCH 23/35] [ML] change breadcrumb title --- .../routing/routes/data_frame_analytics/models_list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx index 347c55d109c1e0..4a0cbd14d22f63 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx @@ -29,7 +29,7 @@ export const modelsListRouteFactory = (navigateToPath: NavigateToPath): MlRoute getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath), { text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.modelsListLabel', { - defaultMessage: 'Models Management', + defaultMessage: 'Model Management', }), href: '', }, From a41209f7adb51a0f0bd4375f519475a25ff78a22 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 16:10:21 +0200 Subject: [PATCH 24/35] [ML] fetch models config with pipelines --- x-pack/plugins/ml/common/types/inference.ts | 15 +++++- .../models_management/models_list.tsx | 35 ++++---------- .../services/ml_api_service/inference.ts | 10 ++-- .../{index.js => index.ts} | 1 + .../data_frame_analytics/models_provider.ts | 42 +++++++++++++++++ x-pack/plugins/ml/server/routes/inference.ts | 46 +++++++++---------- .../server/routes/schemas/inference_schema.ts | 5 ++ 7 files changed, 99 insertions(+), 55 deletions(-) rename x-pack/plugins/ml/server/models/data_frame_analytics/{index.js => index.ts} (86%) create mode 100644 x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts diff --git a/x-pack/plugins/ml/common/types/inference.ts b/x-pack/plugins/ml/common/types/inference.ts index 2c003aa99e1947..c70ee264e6fc88 100644 --- a/x-pack/plugins/ml/common/types/inference.ts +++ b/x-pack/plugins/ml/common/types/inference.ts @@ -60,9 +60,22 @@ export interface ModelConfigResponse { tags: string; version: string; inference_config?: Record; + pipelines?: Record | null; +} + +export interface PipelineDefinition { + processors?: Array>; + description?: string; } export interface ModelPipelines { model_id: string; - pipelines: Record>; description?: string }>; + pipelines: Record; +} + +/** + * Get inference response from the ES endpoint + */ +export interface InferenceConfigResponse { + trained_model_configs: ModelConfigResponse[]; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 841d00bdb92f1b..05ba8b8a94bda7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -48,7 +48,7 @@ type Stats = Omit; export type ModelItem = ModelConfigResponse & { type?: string; stats?: Stats; - pipelines?: ModelPipelines['pipelines']; + pipelines?: ModelPipelines['pipelines'] | null; }; export type ModelItemFull = Required; @@ -88,9 +88,11 @@ export const ModelsList: FC = () => { */ async function fetchData() { try { - const response = await inferenceApiService.getInferenceModel(); + const response = await inferenceApiService.getInferenceModel(undefined, { + with_pipelines: true, + }); setItems( - response.trained_model_configs.map((v) => ({ + response.map((v) => ({ ...v, ...(typeof v.inference_config === 'object' ? { type: Object.keys(v.inference_config)[0] } @@ -120,28 +122,6 @@ export const ModelsList: FC = () => { }; }, [items]); - const fetchAssociatedPipelines = useCallback( - async (models: ModelItem[]) => { - try { - const pipelines = await inferenceApiService.getInferenceModelPipelines( - models.map((model) => model.model_id) - ); - - for (const { model_id: id, pipelines: modelPipelines } of pipelines) { - const model = models.find((m) => m.model_id === id); - model!.pipelines = modelPipelines; - } - } catch (error) { - toasts.addError(new Error(error.body.message), { - title: i18n.translate('xpack.ml.inference.modelsList.fetchModelPipelinesErrorMessage', { - defaultMessage: 'Failed to fetch associated pipelines', - }), - }); - } - }, - [items] - ); - /** * Fetches models stats and update the original object */ @@ -276,6 +256,9 @@ export const ModelsList: FC = () => { onClick: async (model) => { await prepareModelsForDeletion([model]); }, + enabled: (item) => { + return !item.pipelines; + }, }, ]; @@ -285,7 +268,6 @@ export const ModelsList: FC = () => { delete itemIdToExpandedRowMapValues[item.model_id]; } else { await fetchModelsStats([item]); - await fetchAssociatedPipelines([item]); itemIdToExpandedRowMapValues[item.model_id] = ; } setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); @@ -433,6 +415,7 @@ export const ModelsList: FC = () => { }; const selection: EuiTableSelectionType = { + selectable: (item) => !item.pipelines, onSelectionChange: (selectedItems) => { setSelectedModels(selectedItems); }, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts index 6098c5c7c668ad..0206037b680bb3 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts @@ -5,6 +5,7 @@ */ import { useMemo } from 'react'; +import { HttpFetchQuery } from 'kibana/public'; import { HttpService } from '../http_service'; import { basePath } from './index'; import { useMlKibana } from '../../contexts/kibana'; @@ -20,6 +21,8 @@ export interface InferenceQueryParams { include_model_definition?: boolean; size?: number; tags?: string; + // Custom kibana endpoint query params + with_pipelines?: boolean; } export interface InferenceStatsQueryParams { @@ -27,10 +30,6 @@ export interface InferenceStatsQueryParams { size?: number; } -export interface InferenceConfigResponse { - trained_model_configs: ModelConfigResponse[]; -} - export interface IngestStats { count: number; time_in_millis: number; @@ -64,9 +63,10 @@ export function inferenceApiProvider(httpService: HttpService) { model = modelId.join(','); } - return httpService.http({ + return httpService.http({ path: `${apiBasePath}/inference${model && `/${model}`}`, method: 'GET', + ...(params ? { query: params as HttpFetchQuery } : {}), }); }, diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index.js b/x-pack/plugins/ml/server/models/data_frame_analytics/index.ts similarity index 86% rename from x-pack/plugins/ml/server/models/data_frame_analytics/index.js rename to x-pack/plugins/ml/server/models/data_frame_analytics/index.ts index 1d6b05e4a57593..fc18436ff5216a 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/index.js +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index.ts @@ -5,3 +5,4 @@ */ export { analyticsAuditMessagesProvider } from './analytics_audit_messages'; +export { modelsProvider } from './models_provider'; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts new file mode 100644 index 00000000000000..b2a4ccdab43dc6 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IScopedClusterClient } from 'kibana/server'; +import { PipelineDefinition } from '../../../common/types/inference'; + +export function modelsProvider(client: IScopedClusterClient) { + return { + /** + * Retrieves the map of model ids and associated pipelines. + * @param modelIds + */ + async getModelsPipelines(modelIds: string[]) { + const modelIdsMap = new Map | null>( + modelIds.map((id: string) => [id, null]) + ); + + const { body } = await client.asCurrentUser.ingest.getPipeline(); + + for (const [pipelineName, pipelineDefinition] of Object.entries(body)) { + const { processors } = pipelineDefinition as { processors: Array> }; + + for (const processor of processors) { + const id = processor.inference?.model_id; + if (modelIdsMap.has(id)) { + const obj = modelIdsMap.get(id); + if (obj === null) { + modelIdsMap.set(id, { [pipelineName]: pipelineDefinition }); + } else { + obj![pipelineName] = pipelineDefinition; + } + } + } + } + + return modelIdsMap; + }, + }; +} diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index 9ee99b9de1c8ef..8f0f58e03aed40 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -6,7 +6,13 @@ import { RouteInitialization } from '../types'; import { wrapError } from '../client/error_wrapper'; -import { modelIdSchema, optionalModelIdSchema } from './schemas/inference_schema'; +import { + getInferenceQuerySchema, + modelIdSchema, + optionalModelIdSchema, +} from './schemas/inference_schema'; +import { modelsProvider } from '../models/data_frame_analytics'; +import { InferenceConfigResponse } from '../../common/types/inference'; export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { /** @@ -21,6 +27,7 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { path: '/api/ml/inference/{modelId?}', validate: { params: optionalModelIdSchema, + query: getInferenceQuerySchema, }, options: { tags: ['access:ml:canGetDataFrameAnalytics'], @@ -29,11 +36,22 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { modelId } = request.params; - const { body } = await client.asInternalUser.ml.getTrainedModels({ + const { with_pipelines: withPipelines, ...query } = request.query; + const { body } = await client.asInternalUser.ml.getTrainedModels({ + ...query, ...(modelId ? { model_id: modelId } : {}), }); + const result = body.trained_model_configs; + if (withPipelines) { + const pipelinesResponse = await modelsProvider(client).getModelsPipelines( + result.map(({ model_id: id }: { model_id: string }) => id) + ); + for (const model of result) { + model.pipelines = pipelinesResponse.get(model.model_id)!; + } + } return response.ok({ - body, + body: result, }); } catch (e) { return response.customError(wrapError(e)); @@ -93,27 +111,9 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { modelId } = request.params; - - const modelIdsMap = new Map>( - modelId.split(',').map((id: string) => [id, {}]) - ); - - const { body } = await client.asCurrentUser.ingest.getPipeline(); - - for (const [pipelineName, pipelineDefinition] of Object.entries(body)) { - const { processors } = pipelineDefinition as { processors: Array> }; - - for (const processor of processors) { - const id = processor.inference?.model_id; - const obj = modelIdsMap.get(id); - if (obj) { - obj[pipelineName] = pipelineDefinition; - } - } - } - + const result = await modelsProvider(client).getModelsPipelines(modelId.split(',')); return response.ok({ - body: [...modelIdsMap.entries()].map(([id, pipelines]) => ({ model_id: id, pipelines })), + body: [...result].map(([id, pipelines]) => ({ model_id: id, pipelines })), }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts index 090bb5a4890630..896449be7896a3 100644 --- a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts @@ -19,3 +19,8 @@ export const optionalModelIdSchema = schema.object({ */ modelId: schema.maybe(schema.string()), }); + +export const getInferenceQuerySchema = schema.object({ + size: schema.maybe(schema.string()), + with_pipelines: schema.maybe(schema.string()), +}); From 19d5e5ce83d7bdf4f4b22340fd05bcee3804c4f7 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 17 Aug 2020 16:53:56 +0200 Subject: [PATCH 25/35] [ML] change default size to 1000 --- .../components/models_management/models_list.tsx | 1 + x-pack/plugins/ml/server/routes/inference.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 05ba8b8a94bda7..a622bce45b53a3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -90,6 +90,7 @@ export const ModelsList: FC = () => { try { const response = await inferenceApiService.getInferenceModel(undefined, { with_pipelines: true, + size: 1000, }); setItems( response.map((v) => ({ diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index 8f0f58e03aed40..5ae3db4a8f0c15 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -38,6 +38,7 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { const { modelId } = request.params; const { with_pipelines: withPipelines, ...query } = request.query; const { body } = await client.asInternalUser.ml.getTrainedModels({ + size: 1000, ...query, ...(modelId ? { model_id: modelId } : {}), }); From 2fde1adf32a219358ab87a7c0bcf24f4918313e3 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 10:04:42 +0200 Subject: [PATCH 26/35] [ML] fix collections keys, fix double fetch on initial page load --- .../application/data_frame_analytics/common/analytics.ts | 3 --- .../components/models_management/expanded_row.tsx | 8 ++++---- .../components/models_management/models_list.tsx | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index 88d917243f8665..edee5b0397143b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -329,9 +329,6 @@ export const useRefreshAnalyticsList = ( const subscriptions: Subscription[] = []; if (typeof callback.onRefresh === 'function') { - // initial call to refresh - callback.onRefresh(); - subscriptions.push( distinct$ .pipe(filter((state) => state === REFRESH_ANALYTICS_LIST_STATE.REFRESH)) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 4fa8ed505904d8..54c5fda604dcd9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -184,7 +184,7 @@ export const ExpandedRow: FC = ({ item }) => { {formatToListItems(stats.inference_stats).map(({ title, description }) => ( - + ))} @@ -206,7 +206,7 @@ export const ExpandedRow: FC = ({ item }) => { {formatToListItems(stats.ingest.total).map(({ title, description }) => ( - + ))} @@ -233,7 +233,7 @@ export const ExpandedRow: FC = ({ item }) => { {formatToListItems(pipelineStats).map(({ title, description }) => ( - + ))} @@ -258,7 +258,7 @@ export const ExpandedRow: FC = ({ item }) => { {formatToListItems(processorStats).map( ({ title, description }) => ( - + { // Subscribe to the refresh observable to trigger reloading the model list. useRefreshAnalyticsList({ isLoading: setIsLoading, - onRefresh: () => fetchData(), + onRefresh: fetchData, }); /** From 4fdf6b7480c8e461194844077b201aa4743dfcb3 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 10:40:41 +0200 Subject: [PATCH 27/35] [ML] adjust models deletion text --- .../components/models_management/delete_models_modal.tsx | 7 +++++-- .../components/models_management/models_list.tsx | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index d52571d8f782c0..103448d0c764fd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -37,8 +37,11 @@ export const DeleteModelsModal: FC = ({ models, onClose model.model_id).join(', ') }} + defaultMessage="Delete {modelsCount, plural, one {{modelId}} other {# models}}" + values={{ + modelId: models[0].model_id, + modelsCount: models.length, + }} /> diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index cd9404221a359c..1ef9f39c6df904 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -203,7 +203,7 @@ export const ModelsList: FC = () => { toasts.addSuccess( i18n.translate('xpack.ml.inference.modelsList.successfullyDeletedMessage', { defaultMessage: - '{modelsCount, plural, one {Model} other {Models}} {modelsToDeleteIds} {modelsCount, plural, one {has} other {have}} been successfully deleted', + '{modelsCount, plural, one {Model {modelsToDeleteIds}} other {# models}} {modelsCount, plural, one {has} other {have}} been successfully deleted', values: { modelsCount: modelsToDeleteIds.length, modelsToDeleteIds: modelsToDeleteIds.join(', '), From e5e10d145b79b4da1e05fb5e65b21d06b601503f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 13:49:52 +0200 Subject: [PATCH 28/35] [ML] fix DFA jobs on the management page --- .../data_frame_analytics/common/analytics.ts | 6 +++++- .../components/analytics_list/analytics_list.tsx | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index edee5b0397143b..d50e01a37381bd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -321,7 +321,8 @@ export const useRefreshAnalyticsList = ( callback: { isLoading?(d: boolean): void; onRefresh?(): void; - } = {} + } = {}, + isManagementTable = false ) => { useEffect(() => { const distinct$ = refreshAnalyticsList$.pipe(distinctUntilChanged()); @@ -329,6 +330,9 @@ export const useRefreshAnalyticsList = ( const subscriptions: Subscription[] = []; if (typeof callback.onRefresh === 'function') { + // required in order to fetch the DFA jobs on the management page + if (isManagementTable) callback.onRefresh(); + subscriptions.push( distinct$ .pipe(filter((state) => state === REFRESH_ANALYTICS_LIST_STATE.REFRESH)) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 9dcf408a129440..81494a43193dc8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -120,10 +120,13 @@ export const DataFrameAnalyticsList: FC = ({ }, [selectedIdFromUrlInitialized, analytics]); // Subscribe to the refresh observable to trigger reloading the analytics list. - useRefreshAnalyticsList({ - isLoading: setIsLoading, - onRefresh: () => getAnalytics(true), - }); + useRefreshAnalyticsList( + { + isLoading: setIsLoading, + onRefresh: () => getAnalytics(true), + }, + isManagementTable + ); const { columns, modals } = useColumns( expandedRowItemIds, From 0b63d74600db3290a507569e1c9f71b4583bdae7 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 13:58:17 +0200 Subject: [PATCH 29/35] [ML] small tabs in expanded row --- .../components/models_management/expanded_row.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 54c5fda604dcd9..6c0787e13827ee 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -341,6 +341,7 @@ export const ExpandedRow: FC = ({ item }) => { return ( Date: Tue, 18 Aug 2020 14:01:00 +0200 Subject: [PATCH 30/35] [ML] fix headers text --- .../components/models_management/models_list.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 1ef9f39c6df904..2fa115e85398ca 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -298,7 +298,7 @@ export const ModelsList: FC = () => { { field: ModelsTableToConfigMapping.id, name: i18n.translate('xpack.ml.inference.modelsList.modelIdHeader', { - defaultMessage: 'Model ID', + defaultMessage: 'ID', }), sortable: true, truncateText: true, @@ -315,7 +315,7 @@ export const ModelsList: FC = () => { { field: ModelsTableToConfigMapping.createdAt, name: i18n.translate('xpack.ml.inference.modelsList.createdAtHeader', { - defaultMessage: 'Created At', + defaultMessage: 'Created at', }), dataType: 'date', render: (date: string) => formatDate(date, TIME_FORMAT), From 6f546b8c6133d6c7de0af52e6dfb36cf58ec77f9 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 16:24:37 +0200 Subject: [PATCH 31/35] [ML] fix models fetching without pipelines get permissions --- .../models_management/delete_models_modal.tsx | 2 +- .../models_management/models_list.tsx | 28 ++++++++++++++----- x-pack/plugins/ml/server/routes/inference.ts | 19 +++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index 103448d0c764fd..83010c684473e6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -27,7 +27,7 @@ interface DeleteModelsModalProps { export const DeleteModelsModal: FC = ({ models, onClose }) => { const modelsWithPipelines = models - .filter((model) => model.stats.pipeline_count > 0) + .filter((model) => !!model.pipelines) .map((model) => model.model_id); return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 2fa115e85398ca..58bf1c8b4e2ef2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -56,9 +56,12 @@ export type ModelItemFull = Required; export const ModelsList: FC = () => { const { services: { - application: { navigateToUrl }, + application: { navigateToUrl, capabilities }, }, } = useMlKibana(); + + const canDeleteDataFrameAnalytics = capabilities.ml.canDeleteDataFrameAnalytics as boolean; + const inferenceApiService = useInferenceApiService(); const { toasts } = useNotifications(); @@ -222,6 +225,9 @@ export const ModelsList: FC = () => { } } + /** + * Table actions + */ const actions: Array> = [ { name: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', { @@ -257,7 +263,10 @@ export const ModelsList: FC = () => { onClick: async (model) => { await prepareModelsForDeletion([model]); }, + available: (item) => canDeleteDataFrameAnalytics, enabled: (item) => { + // TODO check for permissions to delete ingest pipelines. + // ATM undefined means pipelines fetch failed server-side. return !item.pipelines; }, }, @@ -415,12 +424,17 @@ export const ModelsList: FC = () => { setSortDirection(direction); }; - const selection: EuiTableSelectionType = { - selectable: (item) => !item.pipelines, - onSelectionChange: (selectedItems) => { - setSelectedModels(selectedItems); - }, - }; + const isSelectionAllowed = canDeleteDataFrameAnalytics; + + const selection: EuiTableSelectionType | undefined = isSelectionAllowed + ? { + selectable: (item) => !item.pipelines, + onSelectionChange: (selectedItems) => { + setSelectedModels(selectedItems); + }, + } + : undefined; + return ( <> diff --git a/x-pack/plugins/ml/server/routes/inference.ts b/x-pack/plugins/ml/server/routes/inference.ts index 5ae3db4a8f0c15..4c7aabb3654238 100644 --- a/x-pack/plugins/ml/server/routes/inference.ts +++ b/x-pack/plugins/ml/server/routes/inference.ts @@ -43,14 +43,21 @@ export function inferenceRoutes({ router, mlLicense }: RouteInitialization) { ...(modelId ? { model_id: modelId } : {}), }); const result = body.trained_model_configs; - if (withPipelines) { - const pipelinesResponse = await modelsProvider(client).getModelsPipelines( - result.map(({ model_id: id }: { model_id: string }) => id) - ); - for (const model of result) { - model.pipelines = pipelinesResponse.get(model.model_id)!; + try { + if (withPipelines) { + const pipelinesResponse = await modelsProvider(client).getModelsPipelines( + result.map(({ model_id: id }: { model_id: string }) => id) + ); + for (const model of result) { + model.pipelines = pipelinesResponse.get(model.model_id)!; + } } + } catch (e) { + // the user might not have required permissions to fetch pipelines + // eslint-disable-next-line no-console + console.log(e); } + return response.ok({ body: result, }); From 1a7fe350f2c8eb4700e96f3bf73555fdbe9d78ed Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 17:54:40 +0200 Subject: [PATCH 32/35] [ML] stats rendering as a description list --- .../models_management/expanded_row.tsx | 227 +++++++++--------- 1 file changed, 120 insertions(+), 107 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 6c0787e13827ee..8031953535d7c0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -17,10 +17,13 @@ import { EuiFlexItem, EuiCodeBlock, EuiText, + EuiHorizontalRule, EuiFlexGroup, - EuiStat, } from '@elastic/eui'; +// @ts-ignore +import { formatDate } from '@elastic/eui/lib/services/format'; import { ModelItemFull } from './models_list'; +import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; interface ExpandedRowProps { item: ModelItemFull; @@ -55,10 +58,12 @@ export const ExpandedRow: FC = ({ item }) => { function formatToListItems(items: Record) { return Object.entries(items) - .map(([title, value]) => ({ - title, - description: typeof value === 'object' ? JSON.stringify(value) : value, - })) + .map(([title, value]) => { + if (title.includes('timestamp')) { + value = formatDate(value, TIME_FORMAT); + } + return { title, description: typeof value === 'object' ? JSON.stringify(value) : value }; + }) .filter(({ description }) => { return description !== undefined; }); @@ -170,117 +175,125 @@ export const ExpandedRow: FC = ({ item }) => { content: ( <> - {stats.inference_stats && ( - <> - - -
- -
-
- - - {formatToListItems(stats.inference_stats).map(({ title, description }) => ( - - - - ))} - -
- - - )} - {stats.ingest?.total && ( - - -
- -
-
- - - {formatToListItems(stats.ingest.total).map(({ title, description }) => ( - - - - ))} - - - {stats.ingest?.pipelines && ( - <> + + {stats.inference_stats && ( + + + +
+ +
+
+ +
+
+ )} + {stats.ingest?.total && ( + +
- {Object.entries(stats.ingest.pipelines).map( - ([pipelineName, { processors, ...pipelineStats }]) => { - return ( - - -
{pipelineName}
-
- - - {formatToListItems(pipelineStats).map(({ title, description }) => ( - - - - ))} - - - -
- + + + {stats.ingest?.pipelines && ( + <> + + +
+ +
+
+ + {Object.entries(stats.ingest.pipelines).map( + ([pipelineName, { processors, ...pipelineStats }], i) => { + return ( + + + + +
+ {i + 1}. {pipelineName} +
+
+
+ + + +
+ + -
-
- {processors.map((processor) => { - const name = Object.keys(processor)[0]; - const { stats: processorStats } = processor[name]; - return ( - - -
{name}
-
- - {formatToListItems(processorStats).map( - ({ title, description }) => ( - - - - ) - )} - - -
- ); - })} - -
- ); - } + + +
+ +
+
+ + <> + {processors.map((processor) => { + const name = Object.keys(processor)[0]; + const { stats: processorStats } = processor[name]; + return ( + + + + +
{name}
+
+
+ + + +
+ + +
+ ); + })} + + + ); + } + )} + )} - - )} -
- )} +
+
+ )} +
), }, From a7e21c0cbe67faccb645b3dcd26b77b50158e97e Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 18 Aug 2020 19:15:25 +0200 Subject: [PATCH 33/35] [ML] fix i18n id --- .../components/models_management/expanded_row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 8031953535d7c0..199f438dd4556b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -252,7 +252,7 @@ export const ExpandedRow: FC = ({ item }) => {
From e8a46b3afaf4847bdefe7cdff1f0bb24d6c164e2 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 19 Aug 2020 10:28:51 +0200 Subject: [PATCH 34/35] [ML] remove an extra copyright comment, add selectable messages --- .../components/models_management/models_list.tsx | 9 +++++++++ .../routing/routes/data_frame_analytics/models_list.tsx | 6 ------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 58bf1c8b4e2ef2..5dd134c740f2a6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -428,6 +428,15 @@ export const ModelsList: FC = () => { const selection: EuiTableSelectionType | undefined = isSelectionAllowed ? { + selectableMessage: (selectable, item) => { + return selectable + ? i18n.translate('xpack.ml.inference.modelsList.selectableMessage', { + defaultMessage: 'Select a model', + }) + : i18n.translate('xpack.ml.inference.modelsList.disableSelectableMessage', { + defaultMessage: 'Model has associated pipelines', + }); + }, selectable: (item) => !item.pipelines, onSelectionChange: (selectedItems) => { setSelectedModels(selectedItems); diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx index 4a0cbd14d22f63..7bf7784d1b5598 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx @@ -4,12 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; From 5985295f9869309c7747c7492421cf7b43becb4a Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 19 Aug 2020 14:04:36 +0200 Subject: [PATCH 35/35] [ML] update stats on refresh --- .../data_frame_analytics/common/analytics.ts | 8 +- .../models_management/expanded_row.tsx | 13 ++- .../models_management/models_list.tsx | 105 ++++++++++-------- 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index d50e01a37381bd..8ad861e616b7a9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -336,7 +336,11 @@ export const useRefreshAnalyticsList = ( subscriptions.push( distinct$ .pipe(filter((state) => state === REFRESH_ANALYTICS_LIST_STATE.REFRESH)) - .subscribe(() => typeof callback.onRefresh === 'function' && callback.onRefresh()) + .subscribe(() => { + if (typeof callback.onRefresh === 'function') { + callback.onRefresh(); + } + }) ); } @@ -353,7 +357,7 @@ export const useRefreshAnalyticsList = ( return () => { subscriptions.map((sub) => sub.unsubscribe()); }; - }, []); + }, [callback.onRefresh]); return { refresh: () => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx index 199f438dd4556b..7b9329fee783b5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx @@ -19,6 +19,7 @@ import { EuiText, EuiHorizontalRule, EuiFlexGroup, + EuiTextColor, } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; @@ -233,9 +234,11 @@ export const ExpandedRow: FC = ({ item }) => { -
- {i + 1}. {pipelineName} -
+ +
+ {i + 1}. {pipelineName} +
+
@@ -267,7 +270,9 @@ export const ExpandedRow: FC = ({ item }) => { -
{name}
+ +
{name}
+
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 5dd134c740f2a6..3104ec55c3a6d0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -78,33 +78,51 @@ export const ModelsList: FC = () => { const [modelsToDelete, setModelsToDelete] = useState([]); - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); - - // Subscribe to the refresh observable to trigger reloading the model list. - useRefreshAnalyticsList({ - isLoading: setIsLoading, - onRefresh: fetchData, - }); + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>( + {} + ); /** * Fetches inference trained models. */ - async function fetchData() { + const fetchData = useCallback(async () => { try { const response = await inferenceApiService.getInferenceModel(undefined, { with_pipelines: true, size: 1000, }); - setItems( - response.map((v) => ({ - ...v, - ...(typeof v.inference_config === 'object' - ? { type: Object.keys(v.inference_config)[0] } + + const newItems = []; + const expandedItemsToRefresh = []; + + for (const model of response) { + const tableItem = { + ...model, + ...(typeof model.inference_config === 'object' + ? { type: Object.keys(model.inference_config)[0] } : {}), - })) - ); + }; + newItems.push(tableItem); + + if (itemIdToExpandedRowMap[model.model_id]) { + expandedItemsToRefresh.push(tableItem); + } + } + + setItems(newItems); + + if (expandedItemsToRefresh.length > 0) { + await fetchModelsStats(expandedItemsToRefresh); + + setItemIdToExpandedRowMap( + expandedItemsToRefresh.reduce((acc, item) => { + acc[item.model_id] = ; + return acc; + }, {} as Record) + ); + } } catch (error) { - toasts.addError(new Error(error.body.message), { + toasts.addError(new Error(error.body?.message), { title: i18n.translate('xpack.ml.inference.modelsList.fetchFailedErrorMessage', { defaultMessage: 'Models fetch failed', }), @@ -112,7 +130,13 @@ export const ModelsList: FC = () => { } setIsLoading(false); refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE); - } + }, [itemIdToExpandedRowMap]); + + // Subscribe to the refresh observable to trigger reloading the model list. + useRefreshAnalyticsList({ + isLoading: setIsLoading, + onRefresh: fetchData, + }); const modelsStats: ModelsBarStats = useMemo(() => { return { @@ -129,34 +153,27 @@ export const ModelsList: FC = () => { /** * Fetches models stats and update the original object */ - const fetchModelsStats = useCallback( - async (models: ModelItem[]) => { - const modelIdsToFetch = models - .filter((model) => model.stats === undefined) - .map((model) => model.model_id); - - // no need to fetch - if (modelIdsToFetch.length === 0) return true; - - try { - const { - trained_model_stats: modelsStatsResponse, - } = await inferenceApiService.getInferenceModelStats(modelIdsToFetch); - for (const { model_id: id, ...stats } of modelsStatsResponse) { - const model = models.find((m) => m.model_id === id); - model!.stats = stats; - } - return true; - } catch (error) { - toasts.addError(new Error(error.body.message), { - title: i18n.translate('xpack.ml.inference.modelsList.fetchModelStatsErrorMessage', { - defaultMessage: 'Fetch model stats failed', - }), - }); + const fetchModelsStats = useCallback(async (models: ModelItem[]) => { + const modelIdsToFetch = models.map((model) => model.model_id); + + try { + const { + trained_model_stats: modelsStatsResponse, + } = await inferenceApiService.getInferenceModelStats(modelIdsToFetch); + + for (const { model_id: id, ...stats } of modelsStatsResponse) { + const model = models.find((m) => m.model_id === id); + model!.stats = stats; } - }, - [items] - ); + return true; + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.inference.modelsList.fetchModelStatsErrorMessage', { + defaultMessage: 'Fetch model stats failed', + }), + }); + } + }, []); /** * Unique inference types from models