Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Rare anomaly detection job wizard #100390

Merged
merged 26 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b6d31df
[ML] Rare anomaly detection job wizard
jgowdyelastic May 20, 2021
0ab671c
Merge branch 'master' into rare-job-wizard
kibanamachine May 27, 2021
4166e08
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 1, 2021
e71da5d
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 2, 2021
3298581
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 2, 2021
ec5dffc
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 4, 2021
8fc1a88
fixing fields selection
jgowdyelastic Jun 4, 2021
24824ff
small improvements
jgowdyelastic Jun 4, 2021
4b2b73a
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 7, 2021
0ea7fac
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 8, 2021
9782e9c
adding event rate chart to summary step
jgowdyelastic Jun 8, 2021
1deef6e
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 9, 2021
64ac7e6
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 14, 2021
9fd466c
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 15, 2021
7dda40d
[ML] Changes UI text for rare wizard.
szabosteve Jun 17, 2021
e92d3f7
improving detector summary
jgowdyelastic Jun 17, 2021
cd16fc2
fixing translations
jgowdyelastic Jun 17, 2021
18c6e2d
removing comments
jgowdyelastic Jun 18, 2021
2262218
fixing field selection
jgowdyelastic Jun 21, 2021
50008a0
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 21, 2021
5c494ef
fixing advanced wizard
jgowdyelastic Jun 21, 2021
adb6aa2
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 22, 2021
6f648b1
updating detector text
jgowdyelastic Jun 22, 2021
9ba5c60
fixing bucketspan estimator
jgowdyelastic Jun 22, 2021
0862bb9
bug fixes
jgowdyelastic Jun 22, 2021
aa27210
Merge branch 'master' into rare-job-wizard
kibanamachine Jun 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x-pack/plugins/ml/common/constants/new_job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ export enum JOB_TYPE {
POPULATION = 'population',
ADVANCED = 'advanced',
CATEGORIZATION = 'categorization',
RARE = 'rare',
}

export enum CREATED_BY_LABEL {
SINGLE_METRIC = 'single-metric-wizard',
MULTI_METRIC = 'multi-metric-wizard',
POPULATION = 'population-wizard',
CATEGORIZATION = 'categorization-wizard',
RARE = 'rare-wizard',
APM_TRANSACTION = 'ml-module-apm-transaction',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ export { MultiMetricJobCreator } from './multi_metric_job_creator';
export { PopulationJobCreator } from './population_job_creator';
export { AdvancedJobCreator } from './advanced_job_creator';
export { CategorizationJobCreator } from './categorization_job_creator';
export { RareJobCreator } from './rare_job_creator';
export {
JobCreatorType,
isSingleMetricJobCreator,
isMultiMetricJobCreator,
isPopulationJobCreator,
isAdvancedJobCreator,
isCategorizationJobCreator,
isRareJobCreator,
} from './type_guards';
export { jobCreatorFactory } from './job_creator_factory';
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,9 @@ export class JobCreator {
// change the detector to be a non-zer or non-null count or sum.
// note, the aggregations will always be a standard count or sum and not a non-null or non-zero version
this._detectors.forEach((d, i) => {
if (this._aggs[i] === undefined) {
return;
}
switch (this._aggs[i].id) {
case ML_JOB_AGGREGATION.COUNT:
d.function = this._sparseData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PopulationJobCreator } from './population_job_creator';
import { AdvancedJobCreator } from './advanced_job_creator';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
import { CategorizationJobCreator } from './categorization_job_creator';
import { RareJobCreator } from './rare_job_creator';

import { JOB_TYPE } from '../../../../../../common/constants/new_job';

Expand All @@ -37,6 +38,9 @@ export const jobCreatorFactory = (jobType: JOB_TYPE) => (
case JOB_TYPE.CATEGORIZATION:
jc = CategorizationJobCreator;
break;
case JOB_TYPE.RARE:
jc = RareJobCreator;
break;
default:
jc = SingleMetricJobCreator;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { IndexPattern } from '../../../../../../../../../src/plugins/data/public
export class PopulationJobCreator extends JobCreator {
// a population job has one overall over (split) field, which is the same for all detectors
// each detector has an optional by field
private _splitField: SplitField = null;
private _populatonField: SplitField = null;
private _byFields: SplitField[] = [];
protected _type: JOB_TYPE = JOB_TYPE.POPULATION;

Expand Down Expand Up @@ -65,27 +65,27 @@ export class PopulationJobCreator extends JobCreator {
}

// add an over field to all detectors
public setSplitField(field: SplitField) {
this._splitField = field;
public setPopulationField(field: SplitField) {
this._populatonField = field;

if (this._splitField === null) {
this.removeSplitField();
if (this._populatonField === null) {
this.removePopulationField();
} else {
for (let i = 0; i < this._detectors.length; i++) {
this._detectors[i].over_field_name = this._splitField.id;
this._detectors[i].over_field_name = this._populatonField.id;
}
}
}

// remove over field from all detectors
public removeSplitField() {
public removePopulationField() {
this._detectors.forEach((d) => {
delete d.over_field_name;
});
}

public get splitField(): SplitField {
return this._splitField;
public get populationField(): SplitField {
return this._populatonField;
}

public addDetector(agg: Aggregation, field: Field) {
Expand All @@ -112,8 +112,8 @@ export class PopulationJobCreator extends JobCreator {
private _createDetector(agg: Aggregation, field: Field) {
const dtr: Detector = createBasicDetector(agg, field);

if (this._splitField !== null) {
dtr.over_field_name = this._splitField.id;
if (this._populatonField !== null) {
dtr.over_field_name = this._populatonField.id;
}
return dtr;
}
Expand Down Expand Up @@ -143,7 +143,7 @@ export class PopulationJobCreator extends JobCreator {

if (detectors.length) {
if (detectors[0].overField !== null) {
this.setSplitField(detectors[0].overField);
this.setPopulationField(detectors[0].overField);
}
}
detectors.forEach((d, i) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* 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 { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import { JobCreator } from './job_creator';
import { Field, SplitField } from '../../../../../../common/types/fields';
import { Job, Datafeed } from '../../../../../../common/types/anomaly_detection_jobs';
import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job';
import { getRichDetectors } from './util/general';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
import { isSparseDataJob } from './util/general';
import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types';

export class RareJobCreator extends JobCreator {
private _rareField: Field | null = null;
private _populationField: SplitField = null;
private _splitField: SplitField = null;
peteharverson marked this conversation as resolved.
Show resolved Hide resolved

protected _type: JOB_TYPE = JOB_TYPE.RARE;
private _rareInPopulation: boolean = false;
private _frequentlyRare: boolean = false;

constructor(
indexPattern: IndexPattern,
savedSearch: SavedSearchSavedObject | null,
query: object
) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.RARE;
this._wizardInitialized$.next(true);
}

public setRareField(field: Field | null) {
this._rareField = field;

if (field === null) {
this.removePopulationField();
this.removeSplitField();
this._detectors.length = 0;
return;
}

if (this._detectors.length === 0) {
this._detectors.push({ function: ML_JOB_AGGREGATION.RARE });
}

this._detectors[0].by_field_name = field.id;
}

public get rareField() {
return this._rareField;
}

public get rareInPopulation() {
return this._rareInPopulation;
}

public set rareInPopulation(bool: boolean) {
this._rareInPopulation = bool;
if (bool === false) {
this.removePopulationField();
}
}

public get frequentlyRare() {
return this._frequentlyRare;
}

public set frequentlyRare(bool: boolean) {
this._frequentlyRare = bool;
if (this._detectors.length) {
this._detectors[0].function = bool ? ML_JOB_AGGREGATION.FREQ_RARE : ML_JOB_AGGREGATION.RARE;
}
}

// set the population field, applying it to each detector
public setPopulationField(field: SplitField) {
this._populationField = field;

if (this._populationField === null) {
this.removePopulationField();
} else {
for (let i = 0; i < this._detectors.length; i++) {
this._detectors[i].over_field_name = this._populationField.id;
}
}
}

public removePopulationField() {
this._populationField = null;
this._detectors.forEach((d) => {
delete d.over_field_name;
});
}

public get populationField(): SplitField {
return this._populationField;
}

// set the split field, applying it to each detector
public setSplitField(field: SplitField) {
this._splitField = field;

if (this._splitField === null) {
this.removeSplitField();
} else {
for (let i = 0; i < this._detectors.length; i++) {
this._detectors[i].partition_field_name = this._splitField.id;
}
}
}

public removeSplitField() {
this._detectors.forEach((d) => {
delete d.partition_field_name;
});
}

public get splitField(): SplitField {
return this._splitField;
}

public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.RARE;
this._sparseData = isSparseDataJob(job, datafeed);
const detectors = getRichDetectors(job, datafeed, this.additionalFields, false);

this.removeSplitField();
this.removePopulationField();
this.removeAllDetectors();

if (detectors.length) {
this.setRareField(detectors[0].byField);
this.frequentlyRare = detectors[0].agg?.id === ML_JOB_AGGREGATION.FREQ_RARE;

if (detectors[0].overField !== null) {
this.setPopulationField(detectors[0].overField);
this.rareInPopulation = true;
}
if (detectors[0].partitionField !== null) {
this.setSplitField(detectors[0].partitionField);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import { MultiMetricJobCreator } from './multi_metric_job_creator';
import { PopulationJobCreator } from './population_job_creator';
import { AdvancedJobCreator } from './advanced_job_creator';
import { CategorizationJobCreator } from './categorization_job_creator';
import { RareJobCreator } from './rare_job_creator';
import { JOB_TYPE } from '../../../../../../common/constants/new_job';

export type JobCreatorType =
| SingleMetricJobCreator
| MultiMetricJobCreator
| PopulationJobCreator
| AdvancedJobCreator
| CategorizationJobCreator;
| CategorizationJobCreator
| RareJobCreator;

export function isSingleMetricJobCreator(
jobCreator: JobCreatorType
Expand Down Expand Up @@ -46,3 +48,9 @@ export function isCategorizationJobCreator(
): jobCreator is CategorizationJobCreator {
return jobCreator.type === JOB_TYPE.CATEGORIZATION;
}

export function isRareJobCreator(
jobCreator: JobCreatorType
): jobCreator is CategorizationJobCreator {
return jobCreator.type === JOB_TYPE.RARE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ export function getJobCreatorTitle(jobCreator: JobCreatorType) {
return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.categorization', {
defaultMessage: 'Categorization',
});
case JOB_TYPE.RARE:
return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.rare', {
defaultMessage: 'Rare',
});
default:
return '';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ export function useEstimateBucketSpan() {
indicesOptions: jobCreator.datafeedConfig.indices_options,
};

if (
(isMultiMetricJobCreator(jobCreator) || isPopulationJobCreator(jobCreator)) &&
jobCreator.splitField !== null
) {
if (isMultiMetricJobCreator(jobCreator) && jobCreator.splitField !== null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't get the Estimate bucket span to work on rare jobs - using gallery here and rare uri:

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue is not related to this PR. The bucket span estimator frequently fails me on master when creating a detector on the gallery data set when the partitioning field(s) gives series with lots of sparse data, for example when splitting on clientip or uri. I will create a separate issue for that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #103466 for separate investigation into the bucket span estimator.

data.splitField = jobCreator.splitField.id;
} else if (isPopulationJobCreator(jobCreator) && jobCreator.populationField !== null) {
data.splitField = jobCreator.populationField.id;
} else if (isAdvancedJobCreator(jobCreator)) {
jobCreator.richDetectors.some((d) => {
if (d.partitionField !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import React, { FC, useContext, useEffect, useState, useMemo } from 'react';
import { i18n } from '@kbn/i18n';

import { SplitFieldSelect } from './split_field_select';
import { SplitFieldSelect } from '../split_field_select';
import { JobCreatorContext } from '../../../job_creator_context';
import { Field } from '../../../../../../../../../common/types/fields';
import {
newJobCapsService,
filterCategoryFields,
} from '../../../../../../../services/new_job_capabilities/new_job_capabilities_service';
import { MultiMetricJobCreator, PopulationJobCreator } from '../../../../../common/job_creator';
import { PopulationJobCreator } from '../../../../../common/job_creator';

interface Props {
detectorIndex: number;
Expand Down Expand Up @@ -69,18 +69,18 @@ export const ByFieldSelector: FC<Props> = ({ detectorIndex }) => {
);
};

// remove the split (over) field from the by field options
// remove the population (over) field from the by field options
function useFilteredCategoryFields(
allCategoryFields: Field[],
jobCreator: MultiMetricJobCreator | PopulationJobCreator,
jobCreator: PopulationJobCreator,
jobCreatorUpdated: number
) {
const [fields, setFields] = useState(allCategoryFields);

useEffect(() => {
const sf = jobCreator.splitField;
if (sf !== null) {
setFields(allCategoryFields.filter((f) => f.name !== sf.name));
const pf = jobCreator.populationField;
if (pf !== null) {
setFields(allCategoryFields.filter(({ name }) => name !== pf.name));
} else {
setFields(allCategoryFields);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export { ByFieldSelector } from './by_field';
Loading