diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts index 6fe7d36363280..3bc6d351ad241 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { MlPluginSetup } from '@kbn/ml-plugin/server'; +import { DATAFEED_STATE, JOB_STATE } from '@kbn/ml-plugin/common'; +import { MlAnomalyDetectors } from '@kbn/ml-plugin/server'; import { ApmMlJob } from '../../../common/anomaly_detection/apm_ml_job'; import { Environment } from '../../../common/environment_rt'; import { withApmSpan } from '../../utils/with_apm_span'; @@ -23,11 +24,11 @@ function catch404(e: any) { } export function getMlJobsWithAPMGroup( - anomalyDetectors: ReturnType + anomalyDetectors: MlAnomalyDetectors ): Promise { return withApmSpan('get_ml_jobs_with_apm_group', async () => { try { - const [jobs, allJobStats, allDatafeedStats] = await Promise.all([ + const [jobs, jobStats, datafeedStats] = await Promise.all([ anomalyDetectors .jobs(APM_ML_JOB_GROUP) .then((response) => response.jobs), @@ -41,26 +42,27 @@ export function getMlJobsWithAPMGroup( .catch(catch404), ]); - return jobs.map((job): ApmMlJob => { - const jobStats = allJobStats.find( - (stats) => stats.job_id === job.job_id - ); + const datafeedStateMap = Object.fromEntries( + datafeedStats.map((d) => [d.datafeed_id, d.state as DATAFEED_STATE]) + ); - const datafeedStats = allDatafeedStats.find( - (stats) => stats.datafeed_id === job.datafeed_config?.datafeed_id - ); + const jobStateMap = Object.fromEntries( + jobStats.map((j) => [j.job_id, j.state as JOB_STATE]) + ); + return jobs.map((job): ApmMlJob => { + const jobId = job.job_id; + const datafeedId = job.datafeed_config?.datafeed_id; return { + jobId, + jobState: jobStateMap[jobId], + datafeedId, + datafeedState: datafeedId ? datafeedStateMap[datafeedId] : undefined, + version: Number(job?.custom_settings?.job_tags?.apm_ml_version ?? 1), environment: String( - job.custom_settings?.job_tags?.environment + job?.custom_settings?.job_tags?.environment ) as Environment, - jobId: job.job_id, - jobState: jobStats?.state as ApmMlJob['jobState'], - version: Number(job.custom_settings?.job_tags?.apm_ml_version ?? 1), - datafeedId: datafeedStats?.datafeed_id, - datafeedState: datafeedStats?.state as ApmMlJob['datafeedState'], - // @ts-expect-error bucket_span is of type `estypes.Duration` - bucketSpan: job.analysis_config?.bucket_span, + bucketSpan: job?.analysis_config.bucket_span as string, }; }); } catch (e) { diff --git a/x-pack/plugins/apm/server/lib/helpers/get_ml_client.ts b/x-pack/plugins/apm/server/lib/helpers/get_ml_client.ts index 372df50be6d4d..37f17833ed9da 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_ml_client.ts +++ b/x-pack/plugins/apm/server/lib/helpers/get_ml_client.ts @@ -5,13 +5,18 @@ * 2.0. */ +import { + MlAnomalyDetectors, + MlMlSystem, + MlModules, +} from '@kbn/ml-plugin/server'; import { isActivePlatinumLicense } from '../../../common/license_check'; import { APMRouteHandlerResources } from '../../routes/typings'; export interface MlClient { - mlSystem: any; - anomalyDetectors: any; - modules: any; + mlSystem: MlMlSystem; + anomalyDetectors: MlAnomalyDetectors; + modules: MlModules; } export async function getMlClient({ diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts index f03ada8b0e81b..6a887b21fde74 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts @@ -9,7 +9,7 @@ import Boom from '@hapi/boom'; import { sortBy, uniqBy } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ESSearchResponse } from '@kbn/es-types'; -import { MlPluginSetup } from '@kbn/ml-plugin/server'; +import type { MlAnomalyDetectors } from '@kbn/ml-plugin/server'; import { rangeQuery } from '@kbn/observability-plugin/server'; import { getSeverity, ML_ERRORS } from '../../../common/anomaly_detection'; import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; @@ -154,7 +154,7 @@ export async function getServiceAnomalies({ } export async function getMLJobs( - anomalyDetectors: ReturnType, + anomalyDetectors: MlAnomalyDetectors, environment?: string ) { const jobs = await getMlJobsWithAPMGroup(anomalyDetectors); @@ -173,7 +173,7 @@ export async function getMLJobs( } export async function getMLJobIds( - anomalyDetectors: ReturnType, + anomalyDetectors: MlAnomalyDetectors, environment?: string ) { const mlJobs = await getMLJobs(anomalyDetectors, environment); diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts index 6334ca91bc947..f88297b5074cd 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts @@ -6,6 +6,7 @@ */ import { KibanaRequest } from '@kbn/core/server'; +import { once } from 'lodash'; import type { MlClient } from '../ml_client'; import { mlLog } from '../log'; import { @@ -60,24 +61,28 @@ function disableAdminPrivileges(capabilities: MlCapabilities) { export type HasMlCapabilities = (capabilities: MlCapabilitiesKey[]) => Promise; -export function hasMlCapabilitiesProvider(resolveMlCapabilities: ResolveMlCapabilities) { - return (request: KibanaRequest): HasMlCapabilities => { - let mlCapabilities: MlCapabilities | null = null; - return async (capabilities: MlCapabilitiesKey[]) => { - try { - mlCapabilities = await resolveMlCapabilities(request); - } catch (e) { - mlLog.error(e); - throw new UnknownMLCapabilitiesError(`Unable to perform ML capabilities check ${e}`); - } +export function hasMlCapabilitiesProvider( + resolveMlCapabilities: ResolveMlCapabilities, + request: KibanaRequest +) { + let mlCapabilities: MlCapabilities | null = null; - if (mlCapabilities === null) { - throw new MLPrivilegesUninitialized('ML capabilities have not been initialized'); - } + const resolveMlCapabilitiesOnce = once(resolveMlCapabilities); - if (capabilities.every((c) => mlCapabilities![c] === true) === false) { - throw new InsufficientMLCapabilities('Insufficient privileges to access feature'); - } - }; + return async (capabilities: MlCapabilitiesKey[]) => { + try { + mlCapabilities = await resolveMlCapabilitiesOnce(request); + } catch (e) { + mlLog.error(e); + throw new UnknownMLCapabilitiesError(`Unable to perform ML capabilities check ${e}`); + } + + if (mlCapabilities === null) { + throw new MLPrivilegesUninitialized('ML capabilities have not been initialized'); + } + + if (capabilities.every((c) => mlCapabilities![c] === true) === false) { + throw new InsufficientMLCapabilities('Insufficient privileges to access feature'); + } }; } diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index d660207e16b89..1c31b6d0d088d 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -6,6 +6,7 @@ */ import RE2 from 're2'; +import { memoize } from 'lodash'; import { KibanaRequest, SavedObjectsClientContract, @@ -57,6 +58,11 @@ export function mlSavedObjectServiceFactory( client: IScopedClusterClient, isMlReady: () => Promise ) { + const _savedObjectsClientFindMemo = memoize( + async (options: SavedObjectsFindOptions) => savedObjectsClient.find(options), + (options: SavedObjectsFindOptions) => JSON.stringify(options) + ); + async function _getJobObjects( jobType?: JobType, jobId?: string, @@ -87,8 +93,7 @@ export function mlSavedObjectServiceFactory( filter, }; - const jobs = await savedObjectsClient.find(options); - + const jobs = await _savedObjectsClientFindMemo(options); return jobs.saved_objects; } @@ -231,7 +236,8 @@ export function mlSavedObjectServiceFactory( filter, }; - return (await internalSavedObjectsClient.find(options)).saved_objects; + const jobs = await _savedObjectsClientFindMemo(options); + return jobs.saved_objects; } async function addDatafeed(datafeedId: string, jobId: string) { @@ -462,8 +468,7 @@ export function mlSavedObjectServiceFactory( filter, }; - const models = await savedObjectsClient.find(options); - + const models = await _savedObjectsClientFindMemo(options); return models.saved_objects; } @@ -677,7 +682,7 @@ export function mlSavedObjectServiceFactory( searchFields, filter, }; - return savedObjectsClient.find(options); + return _savedObjectsClientFindMemo(options); }); const finedResult = await Promise.all(searches); diff --git a/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts b/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts index 9ab631e0eda1a..8221734d14092 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts @@ -16,9 +16,10 @@ export function getAlertingServiceProvider(getGuards: GetGuards) { savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest ) { + const guards = getGuards(request, savedObjectsClient); return { preview: async (...args: Parameters) => { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(({ mlClient, scopedClient, getFieldsFormatRegistry, getDataViewsService }) => @@ -33,7 +34,7 @@ export function getAlertingServiceProvider(getGuards: GetGuards) { execute: async ( ...args: Parameters ): ReturnType => { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(({ mlClient, scopedClient, getFieldsFormatRegistry, getDataViewsService }) => diff --git a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts index 92c6dc8513aa7..8887a1dae5b31 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts @@ -32,9 +32,10 @@ export function getAnomalyDetectorsProvider(getGuards: GetGuards): AnomalyDetect request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract ) { + const guards = getGuards(request, savedObjectsClient); return { async jobs(jobId?: string) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ mlClient }) => { @@ -45,7 +46,7 @@ export function getAnomalyDetectorsProvider(getGuards: GetGuards): AnomalyDetect }); }, async jobStats(jobId?: string) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ mlClient }) => { @@ -56,7 +57,7 @@ export function getAnomalyDetectorsProvider(getGuards: GetGuards): AnomalyDetect }); }, async datafeeds(datafeedId?: string) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetDatafeeds']) .ok(async ({ mlClient }) => { @@ -67,7 +68,7 @@ export function getAnomalyDetectorsProvider(getGuards: GetGuards): AnomalyDetect }); }, async datafeedStats(datafeedId?: string) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetDatafeeds']) .ok(async ({ mlClient }) => { diff --git a/x-pack/plugins/ml/server/shared_services/providers/index.ts b/x-pack/plugins/ml/server/shared_services/providers/index.ts new file mode 100644 index 0000000000000..df8feae265b84 --- /dev/null +++ b/x-pack/plugins/ml/server/shared_services/providers/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { MlSystemProvider } from './system'; +export type { JobServiceProvider } from './job_service'; +export type { ModuleSetupPayload, ModulesProvider } from './modules'; +export type { ResultsServiceProvider } from './results_service'; +export type { TrainedModelsProvider } from './trained_models'; +export type { AnomalyDetectorsProvider } from './anomaly_detectors'; + +export { getMlSystemProvider } from './system'; +export { getJobServiceProvider } from './job_service'; +export { getModulesProvider } from './modules'; +export { getResultsServiceProvider } from './results_service'; +export { getTrainedModelsProvider } from './trained_models'; +export { getAnomalyDetectorsProvider } from './anomaly_detectors'; diff --git a/x-pack/plugins/ml/server/shared_services/providers/job_service.ts b/x-pack/plugins/ml/server/shared_services/providers/job_service.ts index c9b4a5c0b0b13..c9d7a5d8d67c6 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/job_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/job_service.ts @@ -25,9 +25,10 @@ export interface JobServiceProvider { export function getJobServiceProvider(getGuards: GetGuards): JobServiceProvider { return { jobServiceProvider(request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) { + const guards = getGuards(request, savedObjectsClient); return { jobsSummary: async (...args) => { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(({ scopedClient, mlClient }) => { @@ -36,7 +37,7 @@ export function getJobServiceProvider(getGuards: GetGuards): JobServiceProvider }); }, forceStartDatafeeds: async (...args) => { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canStartStopDatafeed']) .ok(({ scopedClient, mlClient }) => { @@ -45,7 +46,7 @@ export function getJobServiceProvider(getGuards: GetGuards): JobServiceProvider }); }, stopDatafeeds: async (...args) => { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canStartStopDatafeed']) .ok(({ scopedClient, mlClient }) => { diff --git a/x-pack/plugins/ml/server/shared_services/providers/modules.ts b/x-pack/plugins/ml/server/shared_services/providers/modules.ts index 282715e5e8c17..7180e0c0094e6 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/modules.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/modules.ts @@ -33,9 +33,10 @@ export function getModulesProvider( ): ModulesProvider { return { modulesProvider(request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) { + const guards = getGuards(request, savedObjectsClient); return { async recognize(...args) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ scopedClient, mlClient, mlSavedObjectService, getDataViewsService }) => { @@ -52,7 +53,7 @@ export function getModulesProvider( }); }, async getModule(moduleId: string) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ scopedClient, mlClient, mlSavedObjectService, getDataViewsService }) => { @@ -69,7 +70,7 @@ export function getModulesProvider( }); }, async listModules() { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ scopedClient, mlClient, mlSavedObjectService, getDataViewsService }) => { @@ -86,7 +87,7 @@ export function getModulesProvider( }); }, async setup(payload: ModuleSetupPayload) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canCreateJob']) .ok(async ({ scopedClient, mlClient, mlSavedObjectService, getDataViewsService }) => { diff --git a/x-pack/plugins/ml/server/shared_services/providers/results_service.ts b/x-pack/plugins/ml/server/shared_services/providers/results_service.ts index 496b6b6127bb1..0190410c81197 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/results_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/results_service.ts @@ -23,9 +23,10 @@ export interface ResultsServiceProvider { export function getResultsServiceProvider(getGuards: GetGuards): ResultsServiceProvider { return { resultsServiceProvider(request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) { + const guards = getGuards(request, savedObjectsClient); return { async getAnomaliesTableData(...args) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ mlClient }) => { diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index e205eca325c4c..2bcf76b707f24 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -40,44 +40,41 @@ export function getMlSystemProvider( ): MlSystemProvider { return { mlSystemProvider(request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) { + const guards = getGuards(request, savedObjectsClient); return { async mlCapabilities() { - return await getGuards(request, savedObjectsClient) - .isMinimumLicense() - .ok(async ({ mlClient }) => { - const { isMlEnabledInSpace } = spacesUtilsProvider(getSpaces, request); + return await guards.isMinimumLicense().ok(async ({ mlClient }) => { + const { isMlEnabledInSpace } = spacesUtilsProvider(getSpaces, request); - const mlCapabilities = await resolveMlCapabilities(request); - if (mlCapabilities === null) { - throw new Error('mlCapabilities is not defined'); - } + const mlCapabilities = await resolveMlCapabilities(request); + if (mlCapabilities === null) { + throw new Error('mlCapabilities is not defined'); + } - const { getCapabilities } = capabilitiesProvider( - mlClient, - mlCapabilities, - mlLicense, - isMlEnabledInSpace - ); - return getCapabilities(); - }); + const { getCapabilities } = capabilitiesProvider( + mlClient, + mlCapabilities, + mlLicense, + isMlEnabledInSpace + ); + return getCapabilities(); + }); }, async mlInfo(): Promise { - return await getGuards(request, savedObjectsClient) - .isMinimumLicense() - .ok(async ({ mlClient }) => { - const info = await mlClient.info(); - const cloudId = cloud && cloud.cloudId; - return { - ...info, - cloudId, - }; - }); + return await guards.isMinimumLicense().ok(async ({ mlClient }) => { + const info = await mlClient.info(); + const cloudId = cloud && cloud.cloudId; + return { + ...info, + cloudId, + }; + }); }, async mlAnomalySearch( searchParams: any, jobIds: string[] ): Promise> { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetJobs']) .ok(async ({ mlClient }) => { diff --git a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts index 0a8d80c24ea40..b6a439970cbd7 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts @@ -26,9 +26,10 @@ export interface TrainedModelsProvider { export function getTrainedModelsProvider(getGuards: GetGuards): TrainedModelsProvider { return { trainedModelsProvider(request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) { + const guards = getGuards(request, savedObjectsClient); return { async getTrainedModels(params: estypes.MlGetTrainedModelsRequest) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetTrainedModels']) .ok(async ({ mlClient }) => { @@ -36,7 +37,7 @@ export function getTrainedModelsProvider(getGuards: GetGuards): TrainedModelsPro }); }, async getTrainedModelsStats(params: estypes.MlGetTrainedModelsStatsRequest) { - return await getGuards(request, savedObjectsClient) + return await guards .isFullLicense() .hasMlCapabilities(['canGetTrainedModels']) .ok(async ({ mlClient }) => { diff --git a/x-pack/plugins/ml/server/shared_services/shared_services.ts b/x-pack/plugins/ml/server/shared_services/shared_services.ts index f665bc633825c..752820f57cf2a 100644 --- a/x-pack/plugins/ml/server/shared_services/shared_services.ts +++ b/x-pack/plugins/ml/server/shared_services/shared_services.ts @@ -10,9 +10,10 @@ import type { IScopedClusterClient, SavedObjectsClientContract, UiSettingsServiceStart, + KibanaRequest, } from '@kbn/core/server'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/server'; -import { CoreKibanaRequest, KibanaRequest } from '@kbn/core/server'; +import { CoreKibanaRequest } from '@kbn/core/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; @@ -20,15 +21,24 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import { MlLicense } from '../../common/license'; import { licenseChecks } from './license_checks'; -import { MlSystemProvider, getMlSystemProvider } from './providers/system'; -import { JobServiceProvider, getJobServiceProvider } from './providers/job_service'; -import { ModulesProvider, getModulesProvider } from './providers/modules'; -import { ResultsServiceProvider, getResultsServiceProvider } from './providers/results_service'; -import { TrainedModelsProvider, getTrainedModelsProvider } from './providers/trained_models'; -import { +import type { + MlSystemProvider, + JobServiceProvider, + ResultsServiceProvider, + TrainedModelsProvider, AnomalyDetectorsProvider, + ModulesProvider, +} from './providers'; + +import { + getMlSystemProvider, + getJobServiceProvider, + getModulesProvider, + getResultsServiceProvider, + getTrainedModelsProvider, getAnomalyDetectorsProvider, -} from './providers/anomaly_detectors'; +} from './providers'; + import type { ResolveMlCapabilities, MlCapabilitiesKey } from '../../common/types/capabilities'; import { hasMlCapabilitiesProvider, HasMlCapabilities } from '../lib/capabilities'; import { @@ -200,8 +210,10 @@ function getRequestItemsProvider( getDataViews: () => DataViewsPluginStart ) { return (request: KibanaRequest) => { - const getHasMlCapabilities = hasMlCapabilitiesProvider(resolveMlCapabilities); - let hasMlCapabilities: HasMlCapabilities; + let hasMlCapabilities: HasMlCapabilities = hasMlCapabilitiesProvider( + resolveMlCapabilities, + request + ); let scopedClient: IScopedClusterClient; let mlClient: MlClient; // While https://github.com/elastic/kibana/issues/64588 exists we @@ -247,7 +259,6 @@ function getRequestItemsProvider( let mlSavedObjectService; if (request instanceof CoreKibanaRequest) { - hasMlCapabilities = getHasMlCapabilities(request); scopedClient = clusterClient.asScoped(request); mlSavedObjectService = getSobSavedObjectService(scopedClient); mlClient = getMlClient(scopedClient, mlSavedObjectService);