From 9c3623b037dd91a39c815896dec67a2b2672c488 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 3 Jun 2022 15:15:16 +0200 Subject: [PATCH 1/7] api integration tests, improve type definitions --- .../get_partition_fields_values.ts | 49 +++++--- .../ml/results/get_partition_fields_values.ts | 105 ++++++++++++++++++ .../api_integration/apis/ml/results/index.ts | 1 + 3 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts diff --git a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts index 189aa99e24ce5..b21e384e211ae 100644 --- a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts +++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -6,6 +6,7 @@ */ import Boom from '@hapi/boom'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { PARTITION_FIELDS } from '../../../common/constants/anomalies'; import { PartitionFieldsType } from '../../../common/types/anomalies'; import { CriteriaField } from './results_service'; @@ -18,6 +19,11 @@ type SearchTerm = } | undefined; +export interface PartitionFieldData { + name: string; + values: Array<{ value: string; maxRecordScore?: number }>; +} + /** * Gets an object for aggregation query to retrieve field name and values. * @param fieldType - Field type @@ -110,23 +116,38 @@ function getFieldAgg( * @param fieldType - Field type * @param aggs - Aggregation response */ -function getFieldObject(fieldType: PartitionFieldsType, aggs: any) { - const fieldNameKey = `${fieldType}_name`; - const fieldValueKey = `${fieldType}_value`; +function getFieldObject( + fieldType: PartitionFieldsType, + aggs: Record +): Record | {} { + const fieldNameKey = `${fieldType}_name` as const; + const fieldValueKey = `${fieldType}_value` as const; + + const fieldNameAgg = aggs[fieldNameKey] as estypes.AggregationsMultiTermsAggregate; + const fieldValueAgg = aggs[fieldValueKey] as unknown as { + values: estypes.AggregationsMultiBucketAggregateBase<{ + key: string; + maxRecordScore?: { value: number }; + }>; + }; - return aggs[fieldNameKey].buckets.length > 0 + return fieldNameAgg.buckets.length > 0 ? { [fieldType]: { - name: aggs[fieldNameKey].buckets[0].key, - values: aggs[fieldValueKey].values.buckets.map(({ key, maxRecordScore }: any) => ({ - value: key, - ...(maxRecordScore ? { maxRecordScore: maxRecordScore.value } : {}), - })), + name: Array.isArray(fieldNameAgg.buckets) ? fieldNameAgg.buckets[0].key : '', + values: Array.isArray(fieldValueAgg.values.buckets) + ? fieldValueAgg.values.buckets.map(({ key, maxRecordScore }) => ({ + value: key, + ...(maxRecordScore ? { maxRecordScore: maxRecordScore.value } : {}), + })) + : [], }, } : {}; } +export type PartitionFieldValueResponse = Record; + export const getPartitionFieldsValuesFactory = (mlClient: MlClient) => /** * Gets the record of partition fields with possible values that fit the provided queries. @@ -144,7 +165,7 @@ export const getPartitionFieldsValuesFactory = (mlClient: MlClient) => earliestMs: number, latestMs: number, fieldsConfig: FieldsConfig = {} - ) { + ): Promise { const jobsResponse = await mlClient.getJobs({ job_id: jobId }); if (jobsResponse.count === 0 || jobsResponse.jobs === undefined) { throw Boom.notFound(`Job with the id "${jobId}" not found`); @@ -152,7 +173,7 @@ export const getPartitionFieldsValuesFactory = (mlClient: MlClient) => const job = jobsResponse.jobs[0]; - const isModelPlotEnabled = job?.model_plot_config?.enabled; + const isModelPlotEnabled = !!job?.model_plot_config?.enabled; const isAnomalousOnly = (Object.entries(fieldsConfig) as Array<[string, FieldConfig]>).some( ([k, v]) => { return !!v?.anomalousOnly; @@ -165,14 +186,14 @@ export const getPartitionFieldsValuesFactory = (mlClient: MlClient) => } ); - const isModelPlotSearch = !!isModelPlotEnabled && !isAnomalousOnly; + const isModelPlotSearch = isModelPlotEnabled && !isAnomalousOnly; // Remove the time filter in case model plot is not enabled // and time range is not applied, so // it includes the records that occurred as anomalies historically const searchAllTime = !isModelPlotEnabled && !applyTimeRange; - const requestBody = { + const requestBody: estypes.SearchRequest['body'] = { query: { bool: { filter: [ @@ -230,7 +251,7 @@ export const getPartitionFieldsValuesFactory = (mlClient: MlClient) => return PARTITION_FIELDS.reduce((acc, key) => { return { ...acc, - ...getFieldObject(key, body.aggregations), + ...getFieldObject(key, body.aggregations!), }; }, {}); }; diff --git a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts new file mode 100644 index 0000000000000..e875cc8f9dafa --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts @@ -0,0 +1,105 @@ +/* + * 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. + */ + +import expect from '@kbn/expect'; +import { Datafeed, Job } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + // @ts-expect-error not full interface + const JOB_CONFIG: Job = { + job_id: `fq_multi_1_ae`, + description: + 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', + groups: ['farequote', 'automated', 'multi-metric'], + analysis_config: { + bucket_span: '1h', + influencers: ['airline'], + detectors: [ + { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, + ], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '20mb' }, + model_plot_config: { enabled: true }, + }; + + // @ts-expect-error not full interface + const DATAFEED_CONFIG: Datafeed = { + datafeed_id: 'datafeed-fq_multi_1_ae', + indices: ['ft_farequote'], + job_id: 'fq_multi_1_ae', + query: { bool: { must: [{ match_all: {} }] } }, + }; + + async function createMockJobs() { + await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG); + } + + describe('GetAnomaliesTableData', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await createMockJobs(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('should fetch anomalous only field values within the time range with an empty search term sorting by anomaly score', async () => { + const requestBody = { + jobId: JOB_CONFIG.job_id, + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: {}, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: true, + sort: { by: 'anomaly_score', order: 'desc' }, + }, + }, + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.partition_field.name).to.eql('airline'); + expect(body.partition_field.values.length).to.eql(6); + expect(body.partition_field.values[0].value).to.eql('ACA'); + expect(body.partition_field.values[0].maxRecordScore).to.be.above(50); + expect(body.partition_field.values[1].value).to.eql('JBU'); + expect(body.partition_field.values[1].maxRecordScore).to.be.above(30); + expect(body.partition_field.values[2].value).to.eql('SWR'); + expect(body.partition_field.values[2].maxRecordScore).to.be.above(25); + expect(body.partition_field.values[3].value).to.eql('BAW'); + expect(body.partition_field.values[3].maxRecordScore).to.be.above(10); + expect(body.partition_field.values[4].value).to.eql('TRS'); + expect(body.partition_field.values[4].maxRecordScore).to.be.above(7); + expect(body.partition_field.values[5].value).to.eql('EGF'); + expect(body.partition_field.values[5].maxRecordScore).to.be.above(2); + }); + + it('should fetch all values withing the time range sorting by name', async () => {}); + + it('should fetch anomalous only field value applying the search term', async () => {}); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/results/index.ts b/x-pack/test/api_integration/apis/ml/results/index.ts index 575435fa3a720..a4def88d31daa 100644 --- a/x-pack/test/api_integration/apis/ml/results/index.ts +++ b/x-pack/test/api_integration/apis/ml/results/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get_stopped_partitions')); loadTestFile(require.resolve('./get_category_definition')); loadTestFile(require.resolve('./get_category_examples')); + loadTestFile(require.resolve('./get_partition_fields_values')); }); } From db0aded19f5e4f8f77c954b204c1efcfd69e84da Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 3 Jun 2022 15:21:21 +0200 Subject: [PATCH 2/7] refactor --- .../models/results_service/get_partition_fields_values.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts index b21e384e211ae..48ec6bf9454b5 100644 --- a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts +++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -131,10 +131,10 @@ function getFieldObject( }>; }; - return fieldNameAgg.buckets.length > 0 + return Array.isArray(fieldNameAgg.buckets) && fieldNameAgg.buckets.length > 0 ? { [fieldType]: { - name: Array.isArray(fieldNameAgg.buckets) ? fieldNameAgg.buckets[0].key : '', + name: fieldNameAgg.buckets[0].key, values: Array.isArray(fieldValueAgg.values.buckets) ? fieldValueAgg.values.buckets.map(({ key, maxRecordScore }) => ({ value: key, From d4197b1893010c1cb604df93013a399ab1b693ee Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 3 Jun 2022 15:41:08 +0200 Subject: [PATCH 3/7] more tests --- .../ml/results/get_partition_fields_values.ts | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts index e875cc8f9dafa..b683c2ccb7e1a 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts @@ -98,8 +98,86 @@ export default ({ getService }: FtrProviderContext) => { expect(body.partition_field.values[5].maxRecordScore).to.be.above(2); }); - it('should fetch all values withing the time range sorting by name', async () => {}); + it('should fetch all values withing the time range sorting by name', async () => { + const requestBody = { + jobId: JOB_CONFIG.job_id, + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: {}, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: false, + sort: { by: 'name', order: 'asc' }, + }, + }, + }; - it('should fetch anomalous only field value applying the search term', async () => {}); + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body).to.eql({ + partition_field: { + name: 'airline', + values: [ + { value: 'AAL' }, + { value: 'ACA' }, + { value: 'AMX' }, + { value: 'ASA' }, + { value: 'AWE' }, + { value: 'BAW' }, + { value: 'DAL' }, + { value: 'EGF' }, + { value: 'FFT' }, + { value: 'JAL' }, + { value: 'JBU' }, + { value: 'JZA' }, + { value: 'KLM' }, + { value: 'NKS' }, + { value: 'SWA' }, + { value: 'SWR' }, + { value: 'TRS' }, + { value: 'UAL' }, + { value: 'VRD' }, + ], + }, + }); + }); + + it('should fetch anomalous only field value applying the search term', async () => { + const requestBody = { + jobId: JOB_CONFIG.job_id, + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: { + partition_field: 'JB', + }, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: true, + sort: { by: 'anomaly_score', order: 'asc' }, + }, + }, + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.partition_field.name).to.eql('airline'); + expect(body.partition_field.values.length).to.eql(1); + expect(body.partition_field.values[0].value).to.eql('JBU'); + expect(body.partition_field.values[0].maxRecordScore).to.be.above(30); + }); }); }; From 2615d1a0484ab260e36f6a08ab80d061d5984b0c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 3 Jun 2022 16:23:24 +0200 Subject: [PATCH 4/7] tests for disabled model plot --- .../ml/results/get_partition_fields_values.ts | 126 +++++++++++++----- 1 file changed, 95 insertions(+), 31 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts index b683c2ccb7e1a..9dcf042d38856 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts @@ -16,36 +16,46 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); const ml = getService('ml'); - // @ts-expect-error not full interface - const JOB_CONFIG: Job = { - job_id: `fq_multi_1_ae`, - description: - 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', - groups: ['farequote', 'automated', 'multi-metric'], - analysis_config: { - bucket_span: '1h', - influencers: ['airline'], - detectors: [ - { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, - { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, - { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, - ], - }, - data_description: { time_field: '@timestamp' }, - analysis_limits: { model_memory_limit: '20mb' }, - model_plot_config: { enabled: true }, - }; - - // @ts-expect-error not full interface - const DATAFEED_CONFIG: Datafeed = { - datafeed_id: 'datafeed-fq_multi_1_ae', - indices: ['ft_farequote'], - job_id: 'fq_multi_1_ae', - query: { bool: { must: [{ match_all: {} }] } }, - }; + function getJobConfig(jobId: string, enableModelPlot = true) { + return { + job_id: jobId, + description: + 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', + groups: ['farequote', 'automated', 'multi-metric'], + analysis_config: { + bucket_span: '1h', + influencers: ['airline'], + detectors: [ + { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, + ], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '20mb' }, + model_plot_config: { enabled: enableModelPlot }, + } as Job; + } + + function getDatafeedConfig(jobId: string) { + return { + datafeed_id: `datafeed-${jobId}`, + indices: ['ft_farequote'], + job_id: jobId, + query: { bool: { must: [{ match_all: {} }] } }, + } as Datafeed; + } async function createMockJobs() { - await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG); + await ml.api.createAndRunAnomalyDetectionLookbackJob( + getJobConfig('fq_multi_1_ae'), + getDatafeedConfig('fq_multi_1_ae') + ); + + await ml.api.createAndRunAnomalyDetectionLookbackJob( + getJobConfig('fq_multi_2_ae', false), + getDatafeedConfig('fq_multi_2_ae') + ); } describe('GetAnomaliesTableData', function () { @@ -61,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch anomalous only field values within the time range with an empty search term sorting by anomaly score', async () => { const requestBody = { - jobId: JOB_CONFIG.job_id, + jobId: 'fq_multi_1_ae', criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, @@ -100,7 +110,7 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch all values withing the time range sorting by name', async () => { const requestBody = { - jobId: JOB_CONFIG.job_id, + jobId: 'fq_multi_1_ae', criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, @@ -151,7 +161,7 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch anomalous only field value applying the search term', async () => { const requestBody = { - jobId: JOB_CONFIG.job_id, + jobId: 'fq_multi_1_ae', criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, @@ -179,5 +189,59 @@ export default ({ getService }: FtrProviderContext) => { expect(body.partition_field.values[0].value).to.eql('JBU'); expect(body.partition_field.values[0].maxRecordScore).to.be.above(30); }); + + describe('when model plot is disabled', () => { + it('should fetch results within the time range', async () => { + const requestBody = { + jobId: 'fq_multi_2_ae', + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: {}, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: false, + sort: { by: 'name', order: 'asc' }, + }, + }, + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.partition_field.values.length).to.eql(6); + }); + + it('should fetch results outside the time range', async () => { + const requestBody = { + jobId: 'fq_multi_2_ae', + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: {}, + fieldsConfig: { + partition_field: { + applyTimeRange: false, + anomalousOnly: false, + sort: { by: 'name', order: 'asc' }, + }, + }, + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.partition_field.values.length).to.eql(19); + }); + }); }); }; From ea17d6e25fe523aee11a661faecf669095b4ff62 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 7 Jun 2022 15:45:31 +0200 Subject: [PATCH 5/7] describe section --- .../ml/results/get_partition_fields_values.ts | 226 +++++++++--------- 1 file changed, 114 insertions(+), 112 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts index 9dcf042d38856..f3f9e09939f9b 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts @@ -69,125 +69,127 @@ export default ({ getService }: FtrProviderContext) => { await ml.api.cleanMlIndices(); }); - it('should fetch anomalous only field values within the time range with an empty search term sorting by anomaly score', async () => { - const requestBody = { - jobId: 'fq_multi_1_ae', - criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], - earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT - latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, - searchTerm: {}, - fieldsConfig: { - partition_field: { - applyTimeRange: true, - anomalousOnly: true, - sort: { by: 'anomaly_score', order: 'desc' }, + describe('when model plot is enabled', () => { + it('should fetch anomalous only field values within the time range with an empty search term sorting by anomaly score', async () => { + const requestBody = { + jobId: 'fq_multi_1_ae', + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: {}, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: true, + sort: { by: 'anomaly_score', order: 'desc' }, + }, }, - }, - }; - - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); - - expect(body.partition_field.name).to.eql('airline'); - expect(body.partition_field.values.length).to.eql(6); - expect(body.partition_field.values[0].value).to.eql('ACA'); - expect(body.partition_field.values[0].maxRecordScore).to.be.above(50); - expect(body.partition_field.values[1].value).to.eql('JBU'); - expect(body.partition_field.values[1].maxRecordScore).to.be.above(30); - expect(body.partition_field.values[2].value).to.eql('SWR'); - expect(body.partition_field.values[2].maxRecordScore).to.be.above(25); - expect(body.partition_field.values[3].value).to.eql('BAW'); - expect(body.partition_field.values[3].maxRecordScore).to.be.above(10); - expect(body.partition_field.values[4].value).to.eql('TRS'); - expect(body.partition_field.values[4].maxRecordScore).to.be.above(7); - expect(body.partition_field.values[5].value).to.eql('EGF'); - expect(body.partition_field.values[5].maxRecordScore).to.be.above(2); - }); + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.partition_field.name).to.eql('airline'); + expect(body.partition_field.values.length).to.eql(6); + expect(body.partition_field.values[0].value).to.eql('ACA'); + expect(body.partition_field.values[0].maxRecordScore).to.be.above(50); + expect(body.partition_field.values[1].value).to.eql('JBU'); + expect(body.partition_field.values[1].maxRecordScore).to.be.above(30); + expect(body.partition_field.values[2].value).to.eql('SWR'); + expect(body.partition_field.values[2].maxRecordScore).to.be.above(25); + expect(body.partition_field.values[3].value).to.eql('BAW'); + expect(body.partition_field.values[3].maxRecordScore).to.be.above(10); + expect(body.partition_field.values[4].value).to.eql('TRS'); + expect(body.partition_field.values[4].maxRecordScore).to.be.above(7); + expect(body.partition_field.values[5].value).to.eql('EGF'); + expect(body.partition_field.values[5].maxRecordScore).to.be.above(2); + }); + + it('should fetch all values withing the time range sorting by name', async () => { + const requestBody = { + jobId: 'fq_multi_1_ae', + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: {}, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: false, + sort: { by: 'name', order: 'asc' }, + }, + }, + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); - it('should fetch all values withing the time range sorting by name', async () => { - const requestBody = { - jobId: 'fq_multi_1_ae', - criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], - earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT - latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, - searchTerm: {}, - fieldsConfig: { + expect(body).to.eql({ partition_field: { - applyTimeRange: true, - anomalousOnly: false, - sort: { by: 'name', order: 'asc' }, + name: 'airline', + values: [ + { value: 'AAL' }, + { value: 'ACA' }, + { value: 'AMX' }, + { value: 'ASA' }, + { value: 'AWE' }, + { value: 'BAW' }, + { value: 'DAL' }, + { value: 'EGF' }, + { value: 'FFT' }, + { value: 'JAL' }, + { value: 'JBU' }, + { value: 'JZA' }, + { value: 'KLM' }, + { value: 'NKS' }, + { value: 'SWA' }, + { value: 'SWR' }, + { value: 'TRS' }, + { value: 'UAL' }, + { value: 'VRD' }, + ], }, - }, - }; - - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); - - expect(body).to.eql({ - partition_field: { - name: 'airline', - values: [ - { value: 'AAL' }, - { value: 'ACA' }, - { value: 'AMX' }, - { value: 'ASA' }, - { value: 'AWE' }, - { value: 'BAW' }, - { value: 'DAL' }, - { value: 'EGF' }, - { value: 'FFT' }, - { value: 'JAL' }, - { value: 'JBU' }, - { value: 'JZA' }, - { value: 'KLM' }, - { value: 'NKS' }, - { value: 'SWA' }, - { value: 'SWR' }, - { value: 'TRS' }, - { value: 'UAL' }, - { value: 'VRD' }, - ], - }, + }); }); - }); - it('should fetch anomalous only field value applying the search term', async () => { - const requestBody = { - jobId: 'fq_multi_1_ae', - criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], - earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT - latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, - searchTerm: { - partition_field: 'JB', - }, - fieldsConfig: { - partition_field: { - applyTimeRange: true, - anomalousOnly: true, - sort: { by: 'anomaly_score', order: 'asc' }, + it('should fetch anomalous only field value applying the search term', async () => { + const requestBody = { + jobId: 'fq_multi_1_ae', + criteriaFields: [{ fieldName: 'detector_index', fieldValue: 0 }], + earliestMs: 1454889600000, // February 8, 2016 12:00:00 AM GMT + latestMs: 1454976000000, // February 9, 2016 12:00:00 AM GMT, + searchTerm: { + partition_field: 'JB', + }, + fieldsConfig: { + partition_field: { + applyTimeRange: true, + anomalousOnly: true, + sort: { by: 'anomaly_score', order: 'asc' }, + }, }, - }, - }; - - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); - - expect(body.partition_field.name).to.eql('airline'); - expect(body.partition_field.values.length).to.eql(1); - expect(body.partition_field.values[0].value).to.eql('JBU'); - expect(body.partition_field.values[0].maxRecordScore).to.be.above(30); + }; + + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + + expect(body.partition_field.name).to.eql('airline'); + expect(body.partition_field.values.length).to.eql(1); + expect(body.partition_field.values[0].value).to.eql('JBU'); + expect(body.partition_field.values[0].maxRecordScore).to.be.above(30); + }); }); describe('when model plot is disabled', () => { From d878e0e162e9b1b2d3dddbe627962bd25174b31b Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 7 Jun 2022 15:52:50 +0200 Subject: [PATCH 6/7] runRequest function --- .../ml/results/get_partition_fields_values.ts | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts index f3f9e09939f9b..cdc271837be7b 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { Datafeed, Job } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs'; +import type { PartitionFieldValueResponse } from '@kbn/ml-plugin/server/models/results_service/get_partition_fields_values'; import { USER } from '../../../../functional/services/ml/security_common'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; @@ -58,6 +59,16 @@ export default ({ getService }: FtrProviderContext) => { ); } + async function runRequest(requestBody: object): Promise { + const { body, status } = await supertest + .post(`/api/ml/results/partition_fields_values`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + return body; + } + describe('GetAnomaliesTableData', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); @@ -86,12 +97,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); + const body = await runRequest(requestBody); expect(body.partition_field.name).to.eql('airline'); expect(body.partition_field.values.length).to.eql(6); @@ -125,12 +131,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); + const body = await runRequest(requestBody); expect(body).to.eql({ partition_field: { @@ -178,12 +179,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); + const body = await runRequest(requestBody); expect(body.partition_field.name).to.eql('airline'); expect(body.partition_field.values.length).to.eql(1); @@ -209,13 +205,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); - + const body = await runRequest(requestBody); expect(body.partition_field.values.length).to.eql(6); }); @@ -235,13 +225,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body, status } = await supertest - .post(`/api/ml/results/partition_fields_values`) - .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .send(requestBody); - ml.api.assertResponseStatusCode(200, status, body); - + const body = await runRequest(requestBody); expect(body.partition_field.values.length).to.eql(19); }); }); From d621e02fc736e05af3b81deea344f7745504da92 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 7 Jun 2022 15:54:56 +0200 Subject: [PATCH 7/7] change maxRecordScore assertion --- .../apis/ml/results/get_partition_fields_values.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts index cdc271837be7b..891f3a407da9d 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_partition_fields_values.ts @@ -102,17 +102,17 @@ export default ({ getService }: FtrProviderContext) => { expect(body.partition_field.name).to.eql('airline'); expect(body.partition_field.values.length).to.eql(6); expect(body.partition_field.values[0].value).to.eql('ACA'); - expect(body.partition_field.values[0].maxRecordScore).to.be.above(50); + expect(body.partition_field.values[0].maxRecordScore).to.be.above(0); expect(body.partition_field.values[1].value).to.eql('JBU'); - expect(body.partition_field.values[1].maxRecordScore).to.be.above(30); + expect(body.partition_field.values[1].maxRecordScore).to.be.above(0); expect(body.partition_field.values[2].value).to.eql('SWR'); - expect(body.partition_field.values[2].maxRecordScore).to.be.above(25); + expect(body.partition_field.values[2].maxRecordScore).to.be.above(0); expect(body.partition_field.values[3].value).to.eql('BAW'); - expect(body.partition_field.values[3].maxRecordScore).to.be.above(10); + expect(body.partition_field.values[3].maxRecordScore).to.be.above(0); expect(body.partition_field.values[4].value).to.eql('TRS'); - expect(body.partition_field.values[4].maxRecordScore).to.be.above(7); + expect(body.partition_field.values[4].maxRecordScore).to.be.above(0); expect(body.partition_field.values[5].value).to.eql('EGF'); - expect(body.partition_field.values[5].maxRecordScore).to.be.above(2); + expect(body.partition_field.values[5].maxRecordScore).to.be.above(0); }); it('should fetch all values withing the time range sorting by name', async () => { @@ -184,7 +184,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.partition_field.name).to.eql('airline'); expect(body.partition_field.values.length).to.eql(1); expect(body.partition_field.values[0].value).to.eql('JBU'); - expect(body.partition_field.values[0].maxRecordScore).to.be.above(30); + expect(body.partition_field.values[0].maxRecordScore).to.be.above(0); }); });