diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts index 2eb4242b7931e..440f4c6e55fb8 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts @@ -15,4 +15,9 @@ export type ChunkingConfig = estypes.ChunkingConfig; export type Aggregation = Record; -export type IndicesOptions = estypes.IndicesOptions; +// export type IndicesOptions = estypes.IndicesOptions; +export interface IndicesOptions { + allow_no_indices?: boolean; + expand_wildcards?: estypes.ExpandWildcards; + ignore_unavailable?: boolean; +} diff --git a/x-pack/plugins/ml/common/types/job_service.ts b/x-pack/plugins/ml/common/types/job_service.ts index 5209743f87b3c..44d9d8df5b1ab 100644 --- a/x-pack/plugins/ml/common/types/job_service.ts +++ b/x-pack/plugins/ml/common/types/job_service.ts @@ -34,9 +34,9 @@ export interface BucketSpanEstimatorData { }; fields: Array; index: string; - query: any; - splitField: string | undefined; - timeField: string | undefined; - runtimeMappings: RuntimeMappings | undefined; - indicesOptions: IndicesOptions | undefined; + query?: any; + splitField?: string; + timeField?: string; + runtimeMappings?: RuntimeMappings; + indicesOptions?: IndicesOptions; } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js index a4f9f26641509..9cb4af7085cbe 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js @@ -152,7 +152,7 @@ export class GroupSelector extends Component { } } - const tempJobs = newJobs.map((j) => ({ job_id: j.id, groups: j.newGroups })); + const tempJobs = newJobs.map((j) => ({ jobId: j.id, groups: j.newGroups })); ml.jobs .updateGroups(tempJobs) .then((resp) => { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index 811e9cab365d7..144492eacc247 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -77,7 +77,7 @@ export const jobsApiProvider = (httpService: HttpService) => ({ }); }, - updateGroups(updatedJobs: string[]) { + updateGroups(updatedJobs: Array<{ jobId: string; groups: string[] }>) { const body = JSON.stringify({ jobs: updatedJobs }); return httpService.http({ path: `${ML_BASE_PATH}/jobs/update_groups`, diff --git a/x-pack/plugins/ml/server/lib/route_guard.ts b/x-pack/plugins/ml/server/lib/route_guard.ts index d0a3c59e4d7e5..1a066660d4ee0 100644 --- a/x-pack/plugins/ml/server/lib/route_guard.ts +++ b/x-pack/plugins/ml/server/lib/route_guard.ts @@ -26,14 +26,14 @@ type MLRequestHandlerContext = RequestHandlerContext & { alerting?: AlertingApiRequestHandlerContext; }; -type Handler = (handlerParams: { +type Handler

= (handlerParams: { client: IScopedClusterClient; - request: KibanaRequest; + request: KibanaRequest; response: KibanaResponseFactory; context: MLRequestHandlerContext; jobSavedObjectService: JobSavedObjectService; mlClient: MlClient; -}) => ReturnType; +}) => ReturnType>; type GetMlSavedObjectClient = (request: KibanaRequest) => SavedObjectsClientContract | null; type GetInternalSavedObjectClient = () => SavedObjectsClientContract | null; @@ -62,17 +62,17 @@ export class RouteGuard { this._isMlReady = isMlReady; } - public fullLicenseAPIGuard(handler: Handler) { + public fullLicenseAPIGuard(handler: Handler) { return this._guard(() => this._mlLicense.isFullLicense(), handler); } - public basicLicenseAPIGuard(handler: Handler) { + public basicLicenseAPIGuard(handler: Handler) { return this._guard(() => this._mlLicense.isMinimumLicense(), handler); } - private _guard(check: () => boolean, handler: Handler) { + private _guard(check: () => boolean, handler: Handler) { return ( context: MLRequestHandlerContext, - request: KibanaRequest, + request: KibanaRequest, response: KibanaResponseFactory ) => { if (check() === false) { diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts index 2d5bd1f6f6e45..c5f3c152ddc7a 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts @@ -40,8 +40,8 @@ export interface FieldToBucket { export interface IndexAnnotationArgs { jobIds: string[]; - earliestMs: number; - latestMs: number; + earliestMs: number | null; + latestMs: number | null; maxAnnotations: number; fields?: FieldToBucket[]; detectorIndex?: number; diff --git a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts index d08263f786354..458a34346bed9 100644 --- a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts @@ -68,7 +68,7 @@ export class CalendarManager { * @param calendarIds * @returns {Promise<*>} */ - async getCalendarsByIds(calendarIds: string) { + async getCalendarsByIds(calendarIds: string[]) { const calendars: Calendar[] = await this.getAllCalendars(); return calendars.filter((calendar) => calendarIds.includes(calendar.calendar_id)); } diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 54173d75938d8..8baa7a5d78c6a 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -367,9 +367,9 @@ export class DataVisualizer { aggregatableFields: string[], nonAggregatableFields: string[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings?: RuntimeMappings ) { const stats = { @@ -472,10 +472,10 @@ export class DataVisualizer { query: any, fields: Field[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, - intervalMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, + intervalMs: number | undefined, maxExamples: number, runtimeMappings: RuntimeMappings ): Promise { @@ -529,16 +529,18 @@ export class DataVisualizer { } else { // Will only ever be one document count card, // so no value in batching up the single request. - const stats = await this.getDocumentCountStats( - indexPatternTitle, - query, - timeFieldName, - earliestMs, - latestMs, - intervalMs, - runtimeMappings - ); - batchStats.push(stats); + if (intervalMs !== undefined) { + const stats = await this.getDocumentCountStats( + indexPatternTitle, + query, + timeFieldName, + earliestMs, + latestMs, + intervalMs, + runtimeMappings + ); + batchStats.push(stats); + } } break; case ML_JOB_FIELD_TYPES.KEYWORD: @@ -612,7 +614,7 @@ export class DataVisualizer { query: any, aggregatableFields: string[], samplerShardSize: number, - timeFieldName: string, + timeFieldName: string | undefined, earliestMs?: number, latestMs?: number, datafeedConfig?: Datafeed, @@ -738,9 +740,9 @@ export class DataVisualizer { indexPatternTitle: string, query: any, field: string, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings?: RuntimeMappings ) { const index = indexPatternTitle; @@ -769,9 +771,9 @@ export class DataVisualizer { async getDocumentCountStats( indexPatternTitle: string, query: any, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, intervalMs: number, runtimeMappings: RuntimeMappings ): Promise { @@ -832,9 +834,9 @@ export class DataVisualizer { query: object, fields: Field[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings?: RuntimeMappings ) { const index = indexPatternTitle; @@ -982,9 +984,9 @@ export class DataVisualizer { query: object, fields: Field[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings?: RuntimeMappings ) { const index = indexPatternTitle; @@ -1074,9 +1076,9 @@ export class DataVisualizer { query: object, fields: Field[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings?: RuntimeMappings ) { const index = indexPatternTitle; @@ -1142,9 +1144,9 @@ export class DataVisualizer { query: object, fields: Field[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings?: RuntimeMappings ) { const index = indexPatternTitle; @@ -1211,9 +1213,9 @@ export class DataVisualizer { indexPatternTitle: string, query: any, field: string, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, maxExamples: number, runtimeMappings?: RuntimeMappings ): Promise { diff --git a/x-pack/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts index 2a25087260795..1429324a6d9c8 100644 --- a/x-pack/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -25,6 +25,12 @@ export interface FormFilter { removeItems?: string[]; } +export interface UpdateFilter { + description?: string; + addItems?: string[]; + removeItems?: string[]; +} + export interface FilterRequest { filter_id: string; description?: string; @@ -154,7 +160,7 @@ export class FilterManager { } } - async updateFilter(filterId: string, filter: FormFilter) { + async updateFilter(filterId: string, filter: UpdateFilter) { try { const body: FilterRequest = { filter_id: filterId }; if (filter.description !== undefined) { diff --git a/x-pack/plugins/ml/server/models/filter/index.ts b/x-pack/plugins/ml/server/models/filter/index.ts index 125b846b5c33b..ec6f922ada2f3 100644 --- a/x-pack/plugins/ml/server/models/filter/index.ts +++ b/x-pack/plugins/ml/server/models/filter/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { FilterManager, Filter, FormFilter } from './filter_manager'; +export { FilterManager, Filter, FormFilter, UpdateFilter } from './filter_manager'; diff --git a/x-pack/plugins/ml/server/models/job_service/groups.ts b/x-pack/plugins/ml/server/models/job_service/groups.ts index a1dd2a9fddf8f..02d3771bcb644 100644 --- a/x-pack/plugins/ml/server/models/job_service/groups.ts +++ b/x-pack/plugins/ml/server/models/job_service/groups.ts @@ -7,7 +7,6 @@ import { CalendarManager } from '../calendar'; import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; -import { Job } from '../../../common/types/anomaly_detection_jobs'; import { MlJobsResponse } from '../../../common/types/job_service'; import type { MlClient } from '../../lib/ml_client'; @@ -78,10 +77,10 @@ export function groupsProvider(mlClient: MlClient) { .map((g) => groups[g]); } - async function updateGroups(jobs: Job[]) { + async function updateGroups(jobs: Array<{ jobId: string; groups: string[] }>) { const results: Results = {}; for (const job of jobs) { - const { job_id: jobId, groups } = job; + const { jobId, groups } = job; try { await mlClient.updateJob({ job_id: jobId, body: { groups } }); results[jobId] = { success: true }; diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts index 425dff89032a3..6cb5f67149fb6 100644 --- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts +++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts @@ -30,7 +30,7 @@ export function modelSnapshotProvider(client: IScopedClusterClient, mlClient: Ml replay: boolean, end?: number, deleteInterveningResults: boolean = true, - calendarEvents?: [{ start: number; end: number; description: string }] + calendarEvents?: Array<{ start: number; end: number; description: string }> ) { let datafeedId = `datafeed-${jobId}`; // ensure job exists diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index ed583bd9e3eb1..6adf6fa474cad 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -20,7 +20,8 @@ import { forecastAnomalyDetector, getBucketParamsSchema, getModelSnapshotsSchema, - updateModelSnapshotSchema, + updateModelSnapshotsSchema, + updateModelSnapshotBodySchema, } from './schemas/anomaly_detectors_schema'; /** @@ -179,6 +180,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { const { jobId } = request.params; const { body } = await mlClient.putJob({ job_id: jobId, + // @ts-expect-error job type custom_rules is incorrect body: request.body, }); @@ -274,6 +276,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { path: '/api/ml/anomaly_detectors/{jobId}/_close', validate: { params: jobIdSchema, + query: schema.object({ force: schema.maybe(schema.boolean()) }), }, options: { tags: ['access:ml:canCloseJob'], @@ -312,6 +315,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { path: '/api/ml/anomaly_detectors/{jobId}', validate: { params: jobIdSchema, + query: schema.object({ force: schema.maybe(schema.boolean()) }), }, options: { tags: ['access:ml:canDeleteJob'], @@ -637,15 +641,15 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { * @apiName UpdateModelSnapshotsById * @apiDescription Updates the model snapshot for the specified snapshot ID * - * @apiSchema (params) getModelSnapshotsSchema - * @apiSchema (body) updateModelSnapshotSchema + * @apiSchema (params) updateModelSnapshotsSchema + * @apiSchema (body) updateModelSnapshotBodySchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/model_snapshots/{snapshotId}/_update', validate: { - params: getModelSnapshotsSchema, - body: updateModelSnapshotSchema, + params: updateModelSnapshotsSchema, + body: updateModelSnapshotBodySchema, }, options: { tags: ['access:ml:canCreateJob'], @@ -674,13 +678,13 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { * @apiName GetModelSnapshotsById * @apiDescription Deletes the model snapshot for the specified snapshot ID * - * @apiSchema (params) getModelSnapshotsSchema + * @apiSchema (params) updateModelSnapshotsSchema */ router.delete( { path: '/api/ml/anomaly_detectors/{jobId}/model_snapshots/{snapshotId}', validate: { - params: getModelSnapshotsSchema, + params: updateModelSnapshotsSchema, }, options: { tags: ['access:ml:canCreateJob'], diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.test.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.test.ts index 6c15e6c707a5a..70083a47a4eff 100644 --- a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.test.ts +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.test.ts @@ -61,12 +61,12 @@ describe('schema_extractor', () => { { name: 'mode', documentation: '', - type: 'string', + type: '"auto" | "manual" | "off"', }, { name: 'time_span', documentation: '', - type: 'string', + type: 'string | number', }, ], }, @@ -158,7 +158,7 @@ describe('schema_extractor', () => { { name: 'force', documentation: '', - type: 'any', // string + type: 'boolean', }, ]); }); diff --git a/x-pack/plugins/ml/server/routes/calendars.ts b/x-pack/plugins/ml/server/routes/calendars.ts index 5549ecd8a300d..256ea249096d5 100644 --- a/x-pack/plugins/ml/server/routes/calendars.ts +++ b/x-pack/plugins/ml/server/routes/calendars.ts @@ -36,7 +36,7 @@ function deleteCalendar(mlClient: MlClient, calendarId: string) { return cal.deleteCalendar(calendarId); } -function getCalendarsByIds(mlClient: MlClient, calendarIds: string) { +function getCalendarsByIds(mlClient: MlClient, calendarIds: string[]) { const cal = new CalendarManager(mlClient); return cal.getCalendarsByIds(calendarIds); } @@ -131,6 +131,7 @@ export function calendars({ router, routeGuard }: RouteInitialization) { routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { const body = request.body; + // @ts-expect-error event interface incorrect const resp = await newCalendar(mlClient, body); return response.ok({ @@ -167,6 +168,7 @@ export function calendars({ router, routeGuard }: RouteInitialization) { try { const { calendarId } = request.params; const body = request.body; + // @ts-expect-error event interface incorrect const resp = await updateCalendar(mlClient, calendarId, body); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index d8b88deb50a43..f6dc21538ffa0 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -673,11 +673,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout let results; if (treatAsRoot === 'true' || treatAsRoot === true) { + // @ts-expect-error never used as analyticsId results = await getExtendedMap(mlClient, client, { analyticsId: type !== JOB_MAP_NODE_TYPES.INDEX ? analyticsId : undefined, index: type === JOB_MAP_NODE_TYPES.INDEX ? analyticsId : undefined, }); } else { + // @ts-expect-error never used as analyticsId results = await getAnalyticsMap(mlClient, client, { analyticsId: type !== JOB_MAP_NODE_TYPES.TRAINED_MODEL ? analyticsId : undefined, modelId: type === JOB_MAP_NODE_TYPES.TRAINED_MODEL ? analyticsId : undefined, @@ -714,7 +716,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout routeGuard.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { const { indexPattern } = request.params; - const isRollup = request.query.rollup === 'true'; + const isRollup = request.query?.rollup === 'true'; const savedObjectsClient = context.core.savedObjects.client; const fieldService = fieldServiceProvider( indexPattern, @@ -761,6 +763,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout routeGuard.fullLicenseAPIGuard(async ({ client, request, response }) => { const jobConfig = request.body; try { + // @ts-expect-error DFA schemas are incorrect const results = await validateAnalyticsJob(client, jobConfig); return response.ok({ body: results, diff --git a/x-pack/plugins/ml/server/routes/data_visualizer.ts b/x-pack/plugins/ml/server/routes/data_visualizer.ts index 6dac42876d7df..62a93f16d17fc 100644 --- a/x-pack/plugins/ml/server/routes/data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/data_visualizer.ts @@ -25,9 +25,9 @@ function getOverallStats( aggregatableFields: string[], nonAggregatableFields: string[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, runtimeMappings: RuntimeMappings ) { const dv = new DataVisualizer(client); @@ -50,10 +50,10 @@ function getStatsForFields( query: any, fields: Field[], samplerShardSize: number, - timeFieldName: string, - earliestMs: number, - latestMs: number, - interval: number, + timeFieldName: string | undefined, + earliestMs: number | undefined, + latestMs: number | undefined, + interval: number | undefined, maxExamples: number, runtimeMappings: RuntimeMappings ) { diff --git a/x-pack/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts index 7ba86ac205ed9..1535b12f335a6 100644 --- a/x-pack/plugins/ml/server/routes/filters.ts +++ b/x-pack/plugins/ml/server/routes/filters.ts @@ -8,7 +8,7 @@ import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { createFilterSchema, filterIdSchema, updateFilterSchema } from './schemas/filters_schema'; -import { FilterManager, FormFilter } from '../models/filter'; +import { FilterManager, FormFilter, UpdateFilter } from '../models/filter'; import type { MlClient } from '../lib/ml_client'; // TODO - add function for returning a list of just the filter IDs. @@ -33,7 +33,7 @@ function newFilter(mlClient: MlClient, filter: FormFilter) { return mgr.newFilter(filter); } -function updateFilter(mlClient: MlClient, filterId: string, filter: FormFilter) { +function updateFilter(mlClient: MlClient, filterId: string, filter: UpdateFilter) { const mgr = new FilterManager(mlClient); return mgr.updateFilter(filterId, filter); } diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 39336f192a7f8..2dd85a8772f92 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -10,10 +10,12 @@ import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { categorizationFieldExamplesSchema, - chartSchema, + basicChartSchema, + populationChartSchema, datafeedIdsSchema, forceStartDatafeedSchema, jobIdsSchema, + optionaljobIdsSchema, jobsWithTimerangeSchema, lookBackProgressSchema, topCategoriesSchema, @@ -212,7 +214,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { * For any supplied job IDs, full job information will be returned, which include the analysis configuration, * job stats, datafeed stats, and calendars. * - * @apiSchema (body) jobIdsSchema + * @apiSchema (body) optionaljobIdsSchema * * @apiSuccess {Array} jobsList list of jobs. For any supplied job IDs, the job object will contain a fullJob property * which includes the full configuration and stats for the job. @@ -221,7 +223,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { { path: '/api/ml/jobs/jobs_summary', validate: { - body: jobIdsSchema, + body: optionaljobIdsSchema, }, options: { tags: ['access:ml:canGetJobs'], @@ -259,7 +261,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { { path: '/api/ml/jobs/jobs_with_time_range', validate: { - body: schema.object(jobsWithTimerangeSchema), + body: jobsWithTimerangeSchema, }, options: { tags: ['access:ml:canGetJobs'], @@ -320,13 +322,13 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { * @apiName CreateFullJobsList * @apiDescription Creates a list of jobs * - * @apiSchema (body) jobIdsSchema + * @apiSchema (body) optionaljobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs', validate: { - body: jobIdsSchema, + body: optionaljobIdsSchema, }, options: { tags: ['access:ml:canGetJobs'], @@ -393,7 +395,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { { path: '/api/ml/jobs/update_groups', validate: { - body: schema.object(updateGroupsSchema), + body: updateGroupsSchema, }, options: { tags: ['access:ml:canUpdateJob'], @@ -499,7 +501,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response, context }) => { try { const { indexPattern } = request.params; - const isRollup = request.query.rollup === 'true'; + const isRollup = request.query?.rollup === 'true'; const savedObjectsClient = context.core.savedObjects.client; const { newJobCaps } = jobServiceProvider(client, mlClient); const resp = await newJobCaps(indexPattern, isRollup, savedObjectsClient); @@ -526,7 +528,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { { path: '/api/ml/jobs/new_job_line_chart', validate: { - body: schema.object(chartSchema), + body: schema.object(basicChartSchema), }, options: { tags: ['access:ml:canGetJobs'], @@ -585,7 +587,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { { path: '/api/ml/jobs/new_job_population_chart', validate: { - body: schema.object(chartSchema), + body: schema.object(populationChartSchema), }, options: { tags: ['access:ml:canGetJobs'], @@ -804,6 +806,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { try { + // @ts-ignore schema mismatch const { datafeedId, job, datafeed } = request.body; if (datafeedId !== undefined) { diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index ae5c66f35215b..9309592dfc474 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -80,8 +80,7 @@ export function jobValidationRoutes({ router, mlLicense, routeGuard }: RouteInit const resp = await estimateBucketSpanFactory(client)(request.body) // this catch gets triggered when the estimation code runs without error // but isn't able to come up with a bucket span estimation. - // this doesn't return a HTTP error but an object with an error message - // which the client is then handling. triggering a HTTP error would be + // this doesn't return a HTTP error but an object with an error message a HTTP error would be // too severe for this case. .catch((error: any) => { errorResp = { @@ -156,6 +155,7 @@ export function jobValidationRoutes({ router, mlLicense, routeGuard }: RouteInit }, routeGuard.fullLicenseAPIGuard(async ({ client, request, response }) => { try { + // @ts-expect-error datafeed config is incorrect const resp = await validateCardinality(client, request.body); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts index 0cb784b934528..e327d601555ab 100644 --- a/x-pack/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -45,7 +45,7 @@ function getModule( savedObjectsClient: SavedObjectsClientContract, jobSavedObjectService: JobSavedObjectService, request: KibanaRequest, - moduleId: string + moduleId?: string ) { const dr = new DataRecognizer( client, diff --git a/x-pack/plugins/ml/server/routes/saved_objects.ts b/x-pack/plugins/ml/server/routes/saved_objects.ts index 5c2968ee7c759..b1e1aaf30b12b 100644 --- a/x-pack/plugins/ml/server/routes/saved_objects.ts +++ b/x-pack/plugins/ml/server/routes/saved_objects.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization, SavedObjectsRouteDeps } from '../types'; import { checksFactory, syncSavedObjectsFactory } from '../saved_objects'; @@ -16,7 +17,6 @@ import { canDeleteJobSchema, } from './schemas/saved_objects'; import { spacesUtilsProvider } from '../lib/spaces_utils'; -import { JobType } from '../../common/types/saved_objects'; /** * Routes for job saved object management @@ -213,7 +213,7 @@ export function savedObjectsRoutes( }, routeGuard.fullLicenseAPIGuard(async ({ request, response, jobSavedObjectService }) => { try { - const { jobType, jobIds }: { jobType: JobType; jobIds: string[] } = request.body; + const { jobType, jobIds } = request.body; const { getCurrentSpaceId } = spacesUtilsProvider(getSpaces, request); const currentSpaceId = await getCurrentSpaceId(); @@ -308,7 +308,7 @@ export function savedObjectsRoutes( { path: '/api/ml/saved_objects/can_delete_job/{jobType}', validate: { - params: jobTypeSchema, + params: schema.object({ jobType: jobTypeSchema }), body: canDeleteJobSchema, }, options: { @@ -318,7 +318,7 @@ export function savedObjectsRoutes( routeGuard.fullLicenseAPIGuard(async ({ request, response, jobSavedObjectService, client }) => { try { const { jobType } = request.params; - const { jobIds }: { jobIds: string[] } = request.body; + const { jobIds } = request.body; const { canDeleteJobs } = checksFactory(client, jobSavedObjectService); const body = await canDeleteJobs( diff --git a/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts index 42a97682a81e4..96edbeb0fce0e 100644 --- a/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts @@ -6,13 +6,17 @@ */ import { schema } from '@kbn/config-schema'; +import { ANNOTATION_TYPE } from '../../../common/constants/annotations'; export const indexAnnotationSchema = schema.object({ timestamp: schema.number(), end_timestamp: schema.number(), annotation: schema.string(), job_id: schema.string(), - type: schema.string(), + type: schema.oneOf([ + schema.literal(ANNOTATION_TYPE.ANNOTATION), + schema.literal(ANNOTATION_TYPE.COMMENT), + ]), create_time: schema.maybe(schema.number()), create_username: schema.maybe(schema.string()), modified_time: schema.maybe(schema.number()), @@ -32,8 +36,8 @@ export const indexAnnotationSchema = schema.object({ export const getAnnotationsSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), - earliestMs: schema.oneOf([schema.nullable(schema.number()), schema.maybe(schema.number())]), - latestMs: schema.oneOf([schema.nullable(schema.number()), schema.maybe(schema.number())]), + earliestMs: schema.nullable(schema.number()), + latestMs: schema.nullable(schema.number()), maxAnnotations: schema.number(), /** Fields to find unique values for (e.g. events or created_by) */ fields: schema.maybe( diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index ecf72f121c9c5..4217002e61ef7 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -9,16 +9,22 @@ import { schema } from '@kbn/config-schema'; const customRulesSchema = schema.maybe( schema.arrayOf( - schema.maybe( - schema.object({ - actions: schema.arrayOf(schema.string()), - conditions: schema.maybe(schema.arrayOf(schema.any())), - scope: schema.maybe(schema.any()), - }) - ) + schema.object({ + actions: schema.arrayOf( + schema.oneOf([schema.literal('skip_result'), schema.literal('skip_model_update')]) + ), + conditions: schema.maybe(schema.arrayOf(schema.any())), + scope: schema.maybe(schema.any()), + }) ) ); +const AnalysisLimits = schema.object({ + /** Limit of categorization examples */ + categorization_examples_limit: schema.maybe(schema.number()), + model_memory_limit: schema.string(), +}); + const detectorSchema = schema.object({ identifier: schema.maybe(schema.string()), function: schema.string(), @@ -27,7 +33,14 @@ const detectorSchema = schema.object({ over_field_name: schema.maybe(schema.string()), partition_field_name: schema.maybe(schema.string()), detector_description: schema.maybe(schema.string()), - exclude_frequent: schema.maybe(schema.string()), + exclude_frequent: schema.maybe( + schema.oneOf([ + schema.literal('all'), + schema.literal('none'), + schema.literal('by'), + schema.literal('over'), + ]) + ), use_null: schema.maybe(schema.boolean()), /** Custom rules */ custom_rules: customRulesSchema, @@ -66,22 +79,17 @@ export const anomalyDetectionUpdateJobSchema = schema.object({ ) ), custom_settings: schema.maybe(customSettingsSchema), - analysis_limits: schema.maybe( - schema.object({ - categorization_examples_limit: schema.maybe(schema.number()), - model_memory_limit: schema.maybe(schema.string()), - }) - ), - groups: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + analysis_limits: schema.maybe(AnalysisLimits), + groups: schema.maybe(schema.arrayOf(schema.string())), model_snapshot_retention_days: schema.maybe(schema.number()), daily_model_snapshot_retention_after_days: schema.maybe(schema.number()), }); export const analysisConfigSchema = schema.object({ - bucket_span: schema.maybe(schema.string()), + bucket_span: schema.string(), summary_count_field_name: schema.maybe(schema.string()), detectors: schema.arrayOf(detectorSchema), - influencers: schema.arrayOf(schema.maybe(schema.string())), + influencers: schema.arrayOf(schema.string()), categorization_field_name: schema.maybe(schema.string()), categorization_analyzer: schema.maybe(schema.any()), categorization_filters: schema.maybe(schema.arrayOf(schema.string())), @@ -97,13 +105,7 @@ export const analysisConfigSchema = schema.object({ export const anomalyDetectionJobSchema = { analysis_config: analysisConfigSchema, - analysis_limits: schema.maybe( - schema.object({ - /** Limit of categorization examples */ - categorization_examples_limit: schema.maybe(schema.number()), - model_memory_limit: schema.maybe(schema.string()), - }) - ), + analysis_limits: schema.maybe(AnalysisLimits), background_persist_interval: schema.maybe(schema.string()), create_time: schema.maybe(schema.number()), custom_settings: schema.maybe(customSettingsSchema), @@ -115,7 +117,6 @@ export const anomalyDetectionJobSchema = { time_field: schema.string(), time_format: schema.maybe(schema.string()), }), - datafeed_config: schema.maybe(schema.any()), description: schema.maybe(schema.string()), established_model_memory: schema.maybe(schema.number()), finished_time: schema.maybe(schema.number()), @@ -124,6 +125,7 @@ export const anomalyDetectionJobSchema = { job_version: schema.maybe(schema.string()), groups: schema.arrayOf(schema.maybe(schema.string())), model_plot_config: schema.maybe(schema.any()), + model_plot: schema.maybe(schema.any()), model_size_stats: schema.maybe(schema.any()), model_snapshot_id: schema.maybe(schema.string()), model_snapshot_min_version: schema.maybe(schema.string()), @@ -146,8 +148,8 @@ export const getRecordsSchema = schema.object({ exclude_interim: schema.maybe(schema.boolean()), page: schema.maybe( schema.object({ - from: schema.maybe(schema.number()), - size: schema.maybe(schema.number()), + from: schema.number(), + size: schema.number(), }) ), record_score: schema.maybe(schema.number()), @@ -165,9 +167,9 @@ export const getBucketsSchema = schema.object({ page: schema.maybe( schema.object({ /** Page offset */ - from: schema.maybe(schema.number()), + from: schema.number(), /** Size of the page */ - size: schema.maybe(schema.number()), + size: schema.number(), }) ), sort: schema.maybe(schema.string()), @@ -200,7 +202,14 @@ export const getModelSnapshotsSchema = schema.object({ jobId: schema.string(), }); -export const updateModelSnapshotSchema = schema.object({ +export const updateModelSnapshotsSchema = schema.object({ + /** Snapshot ID */ + snapshotId: schema.string(), + /** Job ID */ + jobId: schema.string(), +}); + +export const updateModelSnapshotBodySchema = schema.object({ /** description */ description: schema.maybe(schema.string()), /** retain */ diff --git a/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts index 42176cb07b624..bc33ac3c57f47 100644 --- a/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts @@ -8,21 +8,19 @@ import { schema } from '@kbn/config-schema'; export const calendarSchema = schema.object({ - calendar_id: schema.maybe(schema.string()), calendarId: schema.string(), - job_ids: schema.arrayOf(schema.maybe(schema.string())), + calendar_id: schema.maybe(schema.string()), + job_ids: schema.arrayOf(schema.string()), description: schema.maybe(schema.string()), total_job_count: schema.maybe(schema.number()), events: schema.arrayOf( - schema.maybe( - schema.object({ - event_id: schema.maybe(schema.string()), - calendar_id: schema.maybe(schema.string()), - description: schema.maybe(schema.string()), - start_time: schema.any(), - end_time: schema.any(), - }) - ) + schema.object({ + event_id: schema.maybe(schema.string()), + calendar_id: schema.maybe(schema.string()), + description: schema.maybe(schema.string()), + start_time: schema.oneOf([schema.string(), schema.number()]), + end_time: schema.oneOf([schema.string(), schema.number()]), + }) ), }); diff --git a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts index e860be59e4eaf..118d2e4140ced 100644 --- a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts @@ -37,8 +37,8 @@ export const datafeedConfigSchema = schema.object({ aggs: schema.maybe(schema.any()), chunking_config: schema.maybe( schema.object({ - mode: schema.maybe(schema.string()), - time_span: schema.maybe(schema.string()), + mode: schema.oneOf([schema.literal('auto'), schema.literal('manual'), schema.literal('off')]), + time_span: schema.maybe(schema.oneOf([schema.string(), schema.number()])), }) ), frequency: schema.maybe(schema.string()), @@ -57,6 +57,4 @@ export const datafeedConfigSchema = schema.object({ export const datafeedIdSchema = schema.object({ datafeedId: schema.string() }); -export const deleteDatafeedQuerySchema = schema.maybe( - schema.object({ force: schema.maybe(schema.any()) }) -); +export const deleteDatafeedQuerySchema = schema.object({ force: schema.maybe(schema.boolean()) }); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts index a642efb5d41f6..8bd05e5b6d682 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts @@ -12,6 +12,4 @@ export const jobAuditMessagesJobIdSchema = schema.object({ jobId: schema.maybe(schema.string()), }); -export const jobAuditMessagesQuerySchema = schema.maybe( - schema.object({ from: schema.maybe(schema.any()) }) -); +export const jobAuditMessagesQuerySchema = schema.object({ from: schema.maybe(schema.string()) }); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index fec6439632129..45ef3e3f73b6e 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -23,62 +23,74 @@ export const categorizationFieldExamplesSchema = { indicesOptions: indicesOptionsSchema, }; -export const chartSchema = { +export const basicChartSchema = { indexPatternTitle: schema.string(), - timeField: schema.maybe(schema.string()), - start: schema.maybe(schema.number()), - end: schema.maybe(schema.number()), + timeField: schema.string(), + start: schema.number(), + end: schema.number(), + intervalMs: schema.number(), + query: schema.any(), + aggFieldNamePairs: schema.arrayOf(schema.any()), + splitFieldName: schema.nullable(schema.string()), + splitFieldValue: schema.nullable(schema.string()), + runtimeMappings: schema.maybe(runtimeMappingsSchema), + indicesOptions: schema.maybe(indicesOptionsSchema), +}; + +export const populationChartSchema = { + indexPatternTitle: schema.string(), + timeField: schema.string(), + start: schema.number(), + end: schema.number(), intervalMs: schema.number(), query: schema.any(), aggFieldNamePairs: schema.arrayOf(schema.any()), - splitFieldName: schema.maybe(schema.nullable(schema.string())), + splitFieldName: schema.nullable(schema.string()), splitFieldValue: schema.maybe(schema.nullable(schema.string())), - runtimeMappings: runtimeMappingsSchema, - indicesOptions: indicesOptionsSchema, + runtimeMappings: schema.maybe(runtimeMappingsSchema), + indicesOptions: schema.maybe(indicesOptionsSchema), }; export const datafeedIdsSchema = schema.object({ - datafeedIds: schema.arrayOf(schema.maybe(schema.string())), + datafeedIds: schema.arrayOf(schema.string()), }); export const forceStartDatafeedSchema = schema.object({ - datafeedIds: schema.arrayOf(schema.maybe(schema.string())), + datafeedIds: schema.arrayOf(schema.string()), start: schema.maybe(schema.number()), end: schema.maybe(schema.number()), }); -export const jobIdSchema = schema.object({ - /** Optional list of job IDs. */ - jobIds: schema.maybe(schema.string()), +export const jobIdsSchema = schema.object({ + /** List of job IDs. */ + jobIds: schema.arrayOf(schema.string()), }); -export const jobIdsSchema = schema.object({ +export const optionaljobIdsSchema = schema.object({ /** Optional list of job IDs. */ - jobIds: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + jobIds: schema.maybe(schema.arrayOf(schema.string())), }); -export const jobsWithTimerangeSchema = { +export const jobsWithTimerangeSchema = schema.object({ dateFormatTz: schema.maybe(schema.string()), -}; +}); export const lookBackProgressSchema = { jobId: schema.string(), - start: schema.maybe(schema.number()), - end: schema.maybe(schema.number()), + start: schema.number(), + end: schema.number(), }; export const topCategoriesSchema = { jobId: schema.string(), count: schema.number() }; -export const updateGroupsSchema = { - jobs: schema.maybe( - schema.arrayOf( - schema.object({ - job_id: schema.maybe(schema.string()), - groups: schema.arrayOf(schema.maybe(schema.string())), - }) - ) +export const updateGroupsSchema = schema.object({ + jobs: schema.arrayOf( + schema.object({ + jobId: schema.string(), + groups: schema.arrayOf(schema.string()), + }) ), -}; +}); export const revertModelSnapshotSchema = schema.object({ jobId: schema.string(), @@ -99,11 +111,11 @@ export const revertModelSnapshotSchema = schema.object({ export const datafeedPreviewSchema = schema.oneOf([ schema.object({ - job: schema.maybe(schema.object(anomalyDetectionJobSchema)), - datafeed: schema.maybe(datafeedConfigSchema), + job: schema.object(anomalyDetectionJobSchema), + datafeed: datafeedConfigSchema, }), schema.object({ - datafeedId: schema.maybe(schema.string()), + datafeedId: schema.string(), }), ]); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts index ddb6800e13fcd..a83bbbff6cec9 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts @@ -9,9 +9,22 @@ import { schema } from '@kbn/config-schema'; import { analysisConfigSchema, anomalyDetectionJobSchema } from './anomaly_detectors_schema'; import { datafeedConfigSchema, indicesOptionsSchema } from './datafeeds_schema'; import { runtimeMappingsSchema } from './runtime_mappings_schema'; +import { ES_AGGREGATION } from '../../../common/constants/aggregation_types'; export const estimateBucketSpanSchema = schema.object({ - aggTypes: schema.arrayOf(schema.nullable(schema.string())), + aggTypes: schema.arrayOf( + schema.nullable( + schema.oneOf([ + schema.literal(ES_AGGREGATION.COUNT), + schema.literal(ES_AGGREGATION.AVG), + schema.literal(ES_AGGREGATION.MAX), + schema.literal(ES_AGGREGATION.MIN), + schema.literal(ES_AGGREGATION.SUM), + schema.literal(ES_AGGREGATION.PERCENTILES), + schema.literal(ES_AGGREGATION.CARDINALITY), + ]) + ) + ), duration: schema.object({ start: schema.number(), end: schema.number() }), fields: schema.arrayOf(schema.nullable(schema.string())), filters: schema.maybe(schema.arrayOf(schema.any())), @@ -19,8 +32,8 @@ export const estimateBucketSpanSchema = schema.object({ query: schema.any(), splitField: schema.maybe(schema.string()), timeField: schema.maybe(schema.string()), - runtimeMappings: runtimeMappingsSchema, - indicesOptions: indicesOptionsSchema, + runtimeMappings: schema.maybe(runtimeMappingsSchema), + indicesOptions: schema.maybe(indicesOptionsSchema), }); export const modelMemoryLimitSchema = schema.object({ @@ -41,7 +54,10 @@ export const validateJobSchema = schema.object({ }) ), fields: schema.maybe(schema.any()), - job: schema.object(anomalyDetectionJobSchema), + job: schema.object({ + ...anomalyDetectionJobSchema, + datafeed_config: datafeedConfigSchema, + }), }); export const validateCardinalitySchema = schema.object({ diff --git a/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts b/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts index 55247a79145c0..36d4ba8cee5e9 100644 --- a/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts @@ -8,16 +8,14 @@ import { schema } from '@kbn/config-schema'; import { isRuntimeField } from '../../../common/util/runtime_field_utils'; -export const runtimeMappingsSchema = schema.maybe( - schema.object( - {}, - { - unknowns: 'allow', - validate: (v: object) => { - if (Object.values(v).some((o) => !isRuntimeField(o))) { - return 'Invalid runtime field'; - } - }, - } - ) +export const runtimeMappingsSchema = schema.object( + {}, + { + unknowns: 'allow', + validate: (v: object) => { + if (Object.values(v).some((o) => !isRuntimeField(o))) { + return 'Invalid runtime field'; + } + }, + } ); diff --git a/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts b/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts index 8233460ecb368..94ee71a1bae19 100644 --- a/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts +++ b/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts @@ -7,24 +7,25 @@ import { schema } from '@kbn/config-schema'; +export const jobTypeSchema = schema.oneOf([ + schema.literal('anomaly-detector'), + schema.literal('data-frame-analytics'), +]); + export const jobsAndSpaces = schema.object({ - jobType: schema.string(), + jobType: jobTypeSchema, jobIds: schema.arrayOf(schema.string()), spaces: schema.arrayOf(schema.string()), }); export const jobsAndCurrentSpace = schema.object({ - jobType: schema.string(), + jobType: jobTypeSchema, jobIds: schema.arrayOf(schema.string()), }); export const syncJobObjects = schema.object({ simulate: schema.maybe(schema.boolean()) }); -export const jobTypeSchema = schema.object({ - jobType: schema.string(), -}); - export const canDeleteJobSchema = schema.object({ /** List of job IDs. */ - jobIds: schema.arrayOf(schema.maybe(schema.string())), + jobIds: schema.arrayOf(schema.string()), }); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts b/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts index 813f890475f57..9b6b9305f8b95 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts @@ -152,7 +152,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.error).to.eql('Bad Request'); expect(body.message).to.eql( - '[request body.analysis_config.detectors]: expected value of type [array] but got [undefined]' + '[request body.analysis_config.bucket_span]: expected value of type [string] but got [undefined]' ); }); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts index 3f349d0b289e8..752aae546565a 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts @@ -240,7 +240,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.error).to.eql('Bad Request'); expect(body.message).to.eql( - '[request body.job.analysis_config.detectors]: expected value of type [array] but got [undefined]' + '[request body.job.analysis_config.bucket_span]: expected value of type [string] but got [undefined]' ); });