[ML] Adding capabilities checks to shared functions (#70069)

* [ML] Adding capabilities checks to shared functions

* small refactor

* disabling capabilities checks for functions called by SIEM alerting

* testing git

* removing comment

* using constant for ml app id

* tiny type clean up

* removing check in ml_capabilities

* fixing types

* removing capabilities checks from ml_capabilities endpoint

* updating types

* better error handling

* improving capabilities check

* adding custom errors

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2020-07-01 17:45:36 +01:00 committed by GitHub
parent b7f33b94a8
commit eafd2af6aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 229 additions and 137 deletions

View file

@ -115,7 +115,7 @@ function getMlSetup(context: APMRequestHandlerContext, request: KibanaRequest) {
const mlClient = ml.mlClient.asScoped(request).callAsCurrentUser;
return {
mlSystem: ml.mlSystemProvider(mlClient, request),
anomalyDetectors: ml.anomalyDetectorsProvider(mlClient),
anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request),
mlClient,
};
}

View file

@ -157,7 +157,7 @@ export class InfraServerPlugin {
plugins.ml?.mlSystemProvider(context.ml?.mlClient.callAsCurrentUser, request);
const mlAnomalyDetectors =
context.ml &&
plugins.ml?.anomalyDetectorsProvider(context.ml?.mlClient.callAsCurrentUser);
plugins.ml?.anomalyDetectorsProvider(context.ml?.mlClient.callAsCurrentUser, request);
const spaceId = plugins.spaces?.spacesService.getSpaceId(request) || 'default';
return {

View file

@ -53,6 +53,7 @@ export const adminMlCapabilities = {
export type UserMlCapabilities = typeof userMlCapabilities;
export type AdminMlCapabilities = typeof adminMlCapabilities;
export type MlCapabilities = UserMlCapabilities & AdminMlCapabilities;
export type MlCapabilitiesKey = keyof MlCapabilities;
export const basicLicenseMlCapabilities = ['canAccessML', 'canFindFileStructure'] as Array<
keyof MlCapabilities

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyAPICaller } from 'kibana/server';
import { getAdminCapabilities, getUserCapabilities } from './__mocks__/ml_capabilities';
import { capabilitiesProvider } from './check_capabilities';
import { MlLicense } from '../../../common/license';
@ -22,8 +23,12 @@ const mlLicenseBasic = {
const mlIsEnabled = async () => true;
const mlIsNotEnabled = async () => false;
const callWithRequestNonUpgrade = async () => ({ upgrade_mode: false });
const callWithRequestUpgrade = async () => ({ upgrade_mode: true });
const callWithRequestNonUpgrade = ((async () => ({
upgrade_mode: false,
})) as unknown) as LegacyAPICaller;
const callWithRequestUpgrade = ((async () => ({
upgrade_mode: true,
})) as unknown) as LegacyAPICaller;
describe('check_capabilities', () => {
describe('getCapabilities() - right number of capabilities', () => {

View file

@ -4,17 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ILegacyScopedClusterClient } from 'kibana/server';
import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { mlLog } from '../../client/log';
import {
MlCapabilities,
adminMlCapabilities,
MlCapabilitiesResponse,
ResolveMlCapabilities,
MlCapabilitiesKey,
} from '../../../common/types/capabilities';
import { upgradeCheckProvider } from './upgrade';
import { MlLicense } from '../../../common/license';
import {
InsufficientMLCapabilities,
UnknownMLCapabilitiesError,
MLPrivilegesUninitialized,
} from './errors';
export function capabilitiesProvider(
callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser'],
callAsCurrentUser: LegacyAPICaller,
capabilities: MlCapabilities,
mlLicense: MlLicense,
isMlEnabledInSpace: () => Promise<boolean>
@ -47,3 +55,27 @@ function disableAdminPrivileges(capabilities: MlCapabilities) {
capabilities.canCreateAnnotation = false;
capabilities.canDeleteAnnotation = false;
}
export type HasMlCapabilities = (capabilities: MlCapabilitiesKey[]) => Promise<void>;
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}`);
}
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');
}
};
};
}

View file

@ -0,0 +1,28 @@
/*
* 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.
*/
/* eslint-disable max-classes-per-file */
export class UnknownMLCapabilitiesError extends Error {
constructor(message?: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
export class InsufficientMLCapabilities extends Error {
constructor(message?: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
export class MLPrivilegesUninitialized extends Error {
constructor(message?: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}

View file

@ -4,5 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { capabilitiesProvider } from './check_capabilities';
export {
capabilitiesProvider,
hasMlCapabilitiesProvider,
HasMlCapabilities,
} from './check_capabilities';
export { setupCapabilitiesSwitcher } from './capabilities_switcher';

View file

@ -4,12 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ILegacyScopedClusterClient } from 'kibana/server';
import { LegacyAPICaller } from 'kibana/server';
import { mlLog } from '../../client/log';
export function upgradeCheckProvider(
callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']
) {
export function upgradeCheckProvider(callAsCurrentUser: LegacyAPICaller) {
async function isUpgradeInProgress(): Promise<boolean> {
let upgradeInProgress = false;
try {

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyCallAPIOptions, ILegacyScopedClusterClient } from 'kibana/server';
import { LegacyCallAPIOptions, LegacyAPICaller } from 'kibana/server';
import _ from 'lodash';
import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types';
import { getSafeAggregationName } from '../../../common/util/job_utils';
@ -113,7 +113,7 @@ export class DataVisualizer {
options?: LegacyCallAPIOptions
) => Promise<any>;
constructor(callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']) {
constructor(callAsCurrentUser: LegacyAPICaller) {
this.callAsCurrentUser = callAsCurrentUser;
}

View file

@ -5,7 +5,7 @@
*/
import Boom from 'boom';
import { ILegacyScopedClusterClient } from 'kibana/server';
import { LegacyAPICaller } from 'kibana/server';
import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules';
@ -58,18 +58,14 @@ interface PartialJob {
}
export class FilterManager {
private _client: ILegacyScopedClusterClient['callAsCurrentUser'];
constructor(client: ILegacyScopedClusterClient['callAsCurrentUser']) {
this._client = client;
}
constructor(private callAsCurrentUser: LegacyAPICaller) {}
async getFilter(filterId: string) {
try {
const [JOBS, FILTERS] = [0, 1];
const results = await Promise.all([
this._client('ml.jobs'),
this._client('ml.filters', { filterId }),
this.callAsCurrentUser('ml.jobs'),
this.callAsCurrentUser('ml.filters', { filterId }),
]);
if (results[FILTERS] && results[FILTERS].filters.length) {
@ -91,7 +87,7 @@ export class FilterManager {
async getAllFilters() {
try {
const filtersResp = await this._client('ml.filters');
const filtersResp = await this.callAsCurrentUser('ml.filters');
return filtersResp.filters;
} catch (error) {
throw Boom.badRequest(error);
@ -101,7 +97,10 @@ export class FilterManager {
async getAllFilterStats() {
try {
const [JOBS, FILTERS] = [0, 1];
const results = await Promise.all([this._client('ml.jobs'), this._client('ml.filters')]);
const results = await Promise.all([
this.callAsCurrentUser('ml.jobs'),
this.callAsCurrentUser('ml.filters'),
]);
// Build a map of filter_ids against jobs and detectors using that filter.
let filtersInUse: FiltersInUse = {};
@ -138,7 +137,7 @@ export class FilterManager {
delete filter.filterId;
try {
// Returns the newly created filter.
return await this._client('ml.addFilter', { filterId, body: filter });
return await this.callAsCurrentUser('ml.addFilter', { filterId, body: filter });
} catch (error) {
throw Boom.badRequest(error);
}
@ -158,7 +157,7 @@ export class FilterManager {
}
// Returns the newly updated filter.
return await this._client('ml.updateFilter', {
return await this.callAsCurrentUser('ml.updateFilter', {
filterId,
body,
});
@ -168,7 +167,7 @@ export class FilterManager {
}
async deleteFilter(filterId: string) {
return this._client('ml.deleteFilter', { filterId });
return this.callAsCurrentUser('ml.deleteFilter', { filterId });
}
buildFiltersInUse(jobsList: PartialJob[]) {

View file

@ -51,7 +51,7 @@ import { registerKibanaSettings } from './lib/register_settings';
declare module 'kibana/server' {
interface RequestHandlerContext {
ml?: {
[PLUGIN_ID]?: {
mlClient: ILegacyScopedClusterClient;
};
}

View file

@ -113,9 +113,6 @@ export function systemRoutes(
{
path: '/api/ml/ml_capabilities',
validate: false,
options: {
tags: ['access:ml:canAccessML'],
},
},
mlLicense.basicLicenseAPIGuard(async (context, request, response) => {
try {

View file

@ -6,3 +6,4 @@
export * from '../common/types/anomalies';
export * from '../common/types/anomaly_detection_jobs';
export * from './lib/capabilities/errors';

View file

@ -4,24 +4,30 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyAPICaller } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { Job } from '../../../common/types/anomaly_detection_jobs';
import { SharedServicesChecks } from '../shared_services';
export interface AnomalyDetectorsProvider {
anomalyDetectorsProvider(
callAsCurrentUser: LegacyAPICaller
callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest
): {
jobs(jobId?: string): Promise<{ count: number; jobs: Job[] }>;
};
}
export function getAnomalyDetectorsProvider(isFullLicense: LicenseCheck): AnomalyDetectorsProvider {
export function getAnomalyDetectorsProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): AnomalyDetectorsProvider {
return {
anomalyDetectorsProvider(callAsCurrentUser: LegacyAPICaller) {
anomalyDetectorsProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
const hasMlCapabilities = getHasMlCapabilities(request);
return {
jobs(jobId?: string) {
async jobs(jobId?: string) {
isFullLicense();
await hasMlCapabilities(['canGetJobs']);
return callAsCurrentUser('ml.jobs', jobId !== undefined ? { jobId } : {});
},
};

View file

@ -4,19 +4,40 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyAPICaller } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { jobServiceProvider } from '../../models/job_service';
import { SharedServicesChecks } from '../shared_services';
type OrigJobServiceProvider = ReturnType<typeof jobServiceProvider>;
export interface JobServiceProvider {
jobServiceProvider(callAsCurrentUser: LegacyAPICaller): ReturnType<typeof jobServiceProvider>;
jobServiceProvider(
callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest
): {
jobsSummary: OrigJobServiceProvider['jobsSummary'];
};
}
export function getJobServiceProvider(isFullLicense: LicenseCheck): JobServiceProvider {
export function getJobServiceProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): JobServiceProvider {
return {
jobServiceProvider(callAsCurrentUser: LegacyAPICaller) {
isFullLicense();
return jobServiceProvider(callAsCurrentUser);
jobServiceProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
// const hasMlCapabilities = getHasMlCapabilities(request);
const { jobsSummary } = jobServiceProvider(callAsCurrentUser);
return {
async jobsSummary(...args) {
isFullLicense();
// Removed while https://github.com/elastic/kibana/issues/64588 exists.
// SIEM are calling this endpoint with a dummy request object from their alerting
// integration and currently alerting does not supply a request object.
// await hasMlCapabilities(['canGetJobs']);
return jobsSummary(...args);
},
};
},
};
}

View file

@ -4,89 +4,59 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyAPICaller, SavedObjectsClientContract } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { DataRecognizer, RecognizeResult } from '../../models/data_recognizer';
import {
Module,
DatafeedOverride,
JobOverride,
DataRecognizerConfigResponse,
} from '../../../common/types/modules';
import { LegacyAPICaller, KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
import { DataRecognizer } from '../../models/data_recognizer';
import { SharedServicesChecks } from '../shared_services';
export interface ModulesProvider {
modulesProvider(
callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest,
savedObjectsClient: SavedObjectsClientContract
): {
recognize(indexPatternTitle: string): Promise<RecognizeResult[]>;
getModule(moduleId?: string): Promise<Module | Module[]>;
saveModuleItems(
moduleId: string,
prefix: string,
groups: string[],
indexPatternName: string,
query: any,
useDedicatedIndex: boolean,
startDatafeed: boolean,
start: number,
end: number,
jobOverrides: JobOverride[],
datafeedOverrides: DatafeedOverride[],
estimateModelMemory?: boolean
): Promise<DataRecognizerConfigResponse>;
recognize: DataRecognizer['findMatches'];
getModule: DataRecognizer['getModule'];
listModules: DataRecognizer['listModules'];
setupModuleItems: DataRecognizer['setupModuleItems'];
};
}
export function getModulesProvider(isFullLicense: LicenseCheck): ModulesProvider {
export function getModulesProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): ModulesProvider {
return {
modulesProvider(
callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest,
savedObjectsClient: SavedObjectsClientContract
) {
isFullLicense();
const hasMlCapabilities = getHasMlCapabilities(request);
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient);
return {
recognize(indexPatternTitle: string) {
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient);
return dr.findMatches(indexPatternTitle);
async recognize(...args) {
isFullLicense();
await hasMlCapabilities(['canCreateJob']);
return dr.findMatches(...args);
},
getModule(moduleId?: string) {
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient);
if (moduleId === undefined) {
return dr.listModules();
} else {
return dr.getModule(moduleId);
}
async getModule(moduleId: string) {
isFullLicense();
await hasMlCapabilities(['canGetJobs']);
return dr.getModule(moduleId);
},
saveModuleItems(
moduleId: string,
prefix: string,
groups: string[],
indexPatternName: string,
query: any,
useDedicatedIndex: boolean,
startDatafeed: boolean,
start: number,
end: number,
jobOverrides: JobOverride[],
datafeedOverrides: DatafeedOverride[],
estimateModelMemory?: boolean
) {
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient);
return dr.setupModuleItems(
moduleId,
prefix,
groups,
indexPatternName,
query,
useDedicatedIndex,
startDatafeed,
start,
end,
jobOverrides,
datafeedOverrides,
estimateModelMemory
);
async listModules() {
isFullLicense();
await hasMlCapabilities(['canGetJobs']);
return dr.listModules();
},
async setupModuleItems(...args) {
isFullLicense();
await hasMlCapabilities(['canCreateJob']);
return dr.setupModuleItems(...args);
},
};
},

View file

@ -4,21 +4,36 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyAPICaller } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { resultsServiceProvider } from '../../models/results_service';
import { SharedServicesChecks } from '../shared_services';
type OrigResultsServiceProvider = ReturnType<typeof resultsServiceProvider>;
export interface ResultsServiceProvider {
resultsServiceProvider(
callAsCurrentUser: LegacyAPICaller
): ReturnType<typeof resultsServiceProvider>;
callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest
): {
getAnomaliesTableData: OrigResultsServiceProvider['getAnomaliesTableData'];
};
}
export function getResultsServiceProvider(isFullLicense: LicenseCheck): ResultsServiceProvider {
export function getResultsServiceProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): ResultsServiceProvider {
return {
resultsServiceProvider(callAsCurrentUser: LegacyAPICaller) {
isFullLicense();
return resultsServiceProvider(callAsCurrentUser);
resultsServiceProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
const hasMlCapabilities = getHasMlCapabilities(request);
const { getAnomaliesTableData } = resultsServiceProvider(callAsCurrentUser);
return {
async getAnomaliesTableData(...args) {
isFullLicense();
await hasMlCapabilities(['canGetJobs']);
return getAnomaliesTableData(...args);
},
};
},
};
}

View file

@ -8,13 +8,13 @@ import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { SearchResponse, SearchParams } from 'elasticsearch';
import { MlServerLicense } from '../../lib/license';
import { CloudSetup } from '../../../../cloud/server';
import { LicenseCheck } from '../license_checks';
import { spacesUtilsProvider } from '../../lib/spaces_utils';
import { SpacesPluginSetup } from '../../../../spaces/server';
import { capabilitiesProvider } from '../../lib/capabilities';
import { MlInfoResponse } from '../../../common/types/ml_server_info';
import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { MlCapabilitiesResponse, ResolveMlCapabilities } from '../../../common/types/capabilities';
import { SharedServicesChecks } from '../shared_services';
export interface MlSystemProvider {
mlSystemProvider(
@ -28,8 +28,7 @@ export interface MlSystemProvider {
}
export function getMlSystemProvider(
isMinimumLicense: LicenseCheck,
isFullLicense: LicenseCheck,
{ isMinimumLicense, isFullLicense, getHasMlCapabilities }: SharedServicesChecks,
mlLicense: MlServerLicense,
spaces: SpacesPluginSetup | undefined,
cloud: CloudSetup | undefined,
@ -37,6 +36,7 @@ export function getMlSystemProvider(
): MlSystemProvider {
return {
mlSystemProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
// const hasMlCapabilities = getHasMlCapabilities(request);
return {
async mlCapabilities() {
isMinimumLicense();
@ -48,7 +48,7 @@ export function getMlSystemProvider(
const mlCapabilities = await resolveMlCapabilities(request);
if (mlCapabilities === null) {
throw new Error('resolveMlCapabilities is not defined');
throw new Error('mlCapabilities is not defined');
}
const { getCapabilities } = capabilitiesProvider(
@ -61,6 +61,7 @@ export function getMlSystemProvider(
},
async mlInfo(): Promise<MlInfoResponse> {
isMinimumLicense();
const info = await callAsCurrentUser('ml.info');
const cloudId = cloud && cloud.cloudId;
return {
@ -70,6 +71,11 @@ export function getMlSystemProvider(
},
async mlAnomalySearch<T>(searchParams: SearchParams): Promise<SearchResponse<T>> {
isFullLicense();
// Removed while https://github.com/elastic/kibana/issues/64588 exists.
// SIEM are calling this endpoint with a dummy request object from their alerting
// integration and currently alerting does not supply a request object.
// await hasMlCapabilities(['canAccessML']);
return callAsCurrentUser('search', {
...searchParams,
index: ML_RESULTS_INDEX_PATTERN,

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { KibanaRequest } from 'kibana/server';
import { MlServerLicense } from '../lib/license';
import { SpacesPluginSetup } from '../../../spaces/server';
@ -18,6 +19,7 @@ import {
getAnomalyDetectorsProvider,
} from './providers/anomaly_detectors';
import { ResolveMlCapabilities } from '../../common/types/capabilities';
import { hasMlCapabilitiesProvider, HasMlCapabilities } from '../lib/capabilities';
export type SharedServices = JobServiceProvider &
AnomalyDetectorsProvider &
@ -25,6 +27,12 @@ export type SharedServices = JobServiceProvider &
ModulesProvider &
ResultsServiceProvider;
export interface SharedServicesChecks {
isFullLicense(): void;
isMinimumLicense(): void;
getHasMlCapabilities(request: KibanaRequest): HasMlCapabilities;
}
export function createSharedServices(
mlLicense: MlServerLicense,
spaces: SpacesPluginSetup | undefined,
@ -32,19 +40,18 @@ export function createSharedServices(
resolveMlCapabilities: ResolveMlCapabilities
): SharedServices {
const { isFullLicense, isMinimumLicense } = licenseChecks(mlLicense);
const getHasMlCapabilities = hasMlCapabilitiesProvider(resolveMlCapabilities);
const checks: SharedServicesChecks = {
isFullLicense,
isMinimumLicense,
getHasMlCapabilities,
};
return {
...getJobServiceProvider(isFullLicense),
...getAnomalyDetectorsProvider(isFullLicense),
...getMlSystemProvider(
isMinimumLicense,
isFullLicense,
mlLicense,
spaces,
cloud,
resolveMlCapabilities
),
...getModulesProvider(isFullLicense),
...getResultsServiceProvider(isFullLicense),
...getJobServiceProvider(checks),
...getAnomalyDetectorsProvider(checks),
...getModulesProvider(checks),
...getResultsServiceProvider(checks),
...getMlSystemProvider(checks, mlLicense, spaces, cloud, resolveMlCapabilities),
};
}

View file

@ -164,9 +164,11 @@ export const signalRulesAlertType = ({
}
const scopedMlCallCluster = services.getScopedCallCluster(ml.mlClient);
const summaryJobs = await ml
.jobServiceProvider(scopedMlCallCluster)
.jobsSummary([machineLearningJobId]);
// Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is
// currently unused by the jobsSummary function.
const summaryJobs = await (
await ml.jobServiceProvider(scopedMlCallCluster, ({} as unknown) as KibanaRequest)
).jobsSummary([machineLearningJobId]);
const jobSummary = summaryJobs.find((job) => job.id === machineLearningJobId);
if (jobSummary == null || !isJobStarted(jobSummary.jobState, jobSummary.datafeedState)) {
@ -184,7 +186,7 @@ export const signalRulesAlertType = ({
const anomalyResults = await findMlSignals({
ml,
callCluster: scopedMlCallCluster,
// This is needed to satisfy the ML Services API, but can be empty as it is
// Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is
// currently unused by the mlAnomalySearch function.
request: ({} as unknown) as KibanaRequest,
jobId: machineLearningJobId,