Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f1ba674
[ML] Categorization wizard
jgowdyelastic Dec 13, 2019
a058464
fixing js prettier issues
jgowdyelastic Dec 16, 2019
caadec2
adding basic category field validation
jgowdyelastic Dec 20, 2019
4b77ad7
adding rare or count selection
jgowdyelastic Dec 20, 2019
8d0795f
fixing types
jgowdyelastic Dec 20, 2019
bf3dafc
category examples changes
jgowdyelastic Jan 2, 2020
f2b4712
improving results search
jgowdyelastic Jan 3, 2020
3c04614
adding analyzer editing
jgowdyelastic Jan 6, 2020
b92b087
improving callout
jgowdyelastic Jan 6, 2020
d38050f
updating callout text
jgowdyelastic Jan 7, 2020
868da11
fixing import path
jgowdyelastic Jan 7, 2020
6f0f363
resetting cat analyser json on flyout open
jgowdyelastic Jan 7, 2020
84acf3c
disabling model plot by default
jgowdyelastic Jan 7, 2020
1955044
minor refactoring
jgowdyelastic Jan 7, 2020
03235c3
fixing types
jgowdyelastic Jan 7, 2020
42cc293
hide estimate bucket span
jgowdyelastic Jan 7, 2020
9b5cce9
setting default bucket span
jgowdyelastic Jan 7, 2020
f982bea
removing ml_classic workaround
jgowdyelastic Jan 8, 2020
8130a34
changing style of detector selection
jgowdyelastic Jan 8, 2020
7593215
fixing convert to advanced issue
jgowdyelastic Jan 8, 2020
529f109
removing sparse data checkbox
jgowdyelastic Jan 8, 2020
2b4f3b8
changes based on review
jgowdyelastic Jan 8, 2020
74d31c8
use default mml
jgowdyelastic Jan 8, 2020
48c1857
fixing job cloning
jgowdyelastic Jan 8, 2020
2141d04
changes based on review
jgowdyelastic Jan 8, 2020
b5d4fca
removing categorization_analyzer from job if it is same as default
jgowdyelastic Jan 8, 2020
81ca64c
fixing translations
jgowdyelastic Jan 8, 2020
e869c4b
disabling model plot for rare jobs
jgowdyelastic Jan 8, 2020
80b802f
removing console.error in useResolver
jgowdyelastic Jan 9, 2020
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
8 changes: 8 additions & 0 deletions x-pack/legacy/plugins/ml/common/constants/new_job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,24 @@ export enum JOB_TYPE {
MULTI_METRIC = 'multi_metric',
POPULATION = 'population',
ADVANCED = 'advanced',
CATEGORIZATION = 'categorization',
}

export enum CREATED_BY_LABEL {
SINGLE_METRIC = 'single-metric-wizard',
MULTI_METRIC = 'multi-metric-wizard',
POPULATION = 'population-wizard',
CATEGORIZATION = 'categorization-wizard',
}

export const DEFAULT_MODEL_MEMORY_LIMIT = '10MB';
export const DEFAULT_BUCKET_SPAN = '15m';
export const DEFAULT_RARE_BUCKET_SPAN = '1h';
export const DEFAULT_QUERY_DELAY = '60s';

export const SHARED_RESULTS_INDEX_NAME = 'shared';

export const NUMBER_OF_CATEGORY_EXAMPLES = 5;
export const CATEGORY_EXAMPLES_MULTIPLIER = 20;
export const CATEGORY_EXAMPLES_WARNING_LIMIT = 0.75;
export const CATEGORY_EXAMPLES_ERROR_LIMIT = 0.2;
25 changes: 25 additions & 0 deletions x-pack/legacy/plugins/ml/common/types/categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export type CategoryId = number;

export interface Category {
job_id: string;
Comment thread
darnautov marked this conversation as resolved.
Outdated
category_id: CategoryId;
terms: string;
regex: string;
max_matching_length: number;
examples: string[];
grok_pattern: string;
}

export interface Token {
token: string;
start_offset: number;
end_offset: number;
type: string;
position: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { isEqual } from 'lodash';
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import { JobCreator } from './job_creator';
import { Field, Aggregation, mlCategory } from '../../../../../../common/types/fields';
import { Job, Datafeed, Detector } from './configs';
import { createBasicDetector } from './util/default_configs';
import {
JOB_TYPE,
CREATED_BY_LABEL,
DEFAULT_BUCKET_SPAN,
DEFAULT_RARE_BUCKET_SPAN,
} from '../../../../../../common/constants/new_job';
import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types';
import { getRichDetectors } from './util/general';
import { CategorizationExamplesLoader, CategoryExample } from '../results_loader';
import { CategorizationAnalyzer, getNewJobDefaults } from '../../../../services/ml_server_info';

type CategorizationAnalyzerType = CategorizationAnalyzer | null;

export class CategorizationJobCreator extends JobCreator {
protected _type: JOB_TYPE = JOB_TYPE.CATEGORIZATION;
private _createCountDetector: () => void = () => {};
private _createRareDetector: () => void = () => {};
private _examplesLoader: CategorizationExamplesLoader;
private _categoryFieldExamples: CategoryExample[] = [];
private _categoryFieldValid: number = 0;
private _detectorType: ML_JOB_AGGREGATION.COUNT | ML_JOB_AGGREGATION.RARE =
ML_JOB_AGGREGATION.COUNT;
private _categorizationAnalyzer: CategorizationAnalyzerType = null;
private _defaultCategorizationAnalyzer: CategorizationAnalyzerType;

constructor(
indexPattern: IndexPattern,
savedSearch: SavedSearchSavedObject | null,
query: object
) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.CATEGORIZATION;
this._examplesLoader = new CategorizationExamplesLoader(this, indexPattern, query);

const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults();
this._defaultCategorizationAnalyzer = anomalyDetectors.categorization_analyzer || null;
}

public setDefaultDetectorProperties(
count: Aggregation | null,
rare: Aggregation | null,
eventRate: Field | null
) {
if (count === null || rare === null || eventRate === null) {
return;
}

this._createCountDetector = () => {
this._createDetector(count, eventRate);
};
this._createRareDetector = () => {
this._createDetector(rare, eventRate);
};
}

private _createDetector(agg: Aggregation, field: Field) {
const dtr: Detector = createBasicDetector(agg, field);
dtr.by_field_name = mlCategory.id;
this._addDetector(dtr, agg, mlCategory);
}

public setDetectorType(type: ML_JOB_AGGREGATION.COUNT | ML_JOB_AGGREGATION.RARE) {
this._detectorType = type;
this.removeAllDetectors();
if (type === ML_JOB_AGGREGATION.COUNT) {
this._createCountDetector();
this.bucketSpan = DEFAULT_BUCKET_SPAN;
} else {
this._createRareDetector();
this.bucketSpan = DEFAULT_RARE_BUCKET_SPAN;
this.modelPlot = false;
}
}

public set categorizationFieldName(fieldName: string | null) {
if (fieldName !== null) {
this._job_config.analysis_config.categorization_field_name = fieldName;
this.setDetectorType(this._detectorType);
this.addInfluencer(mlCategory.id);
} else {
delete this._job_config.analysis_config.categorization_field_name;
this._categoryFieldExamples = [];
this._categoryFieldValid = 0;
}
}

public get categorizationFieldName(): string | null {
return this._job_config.analysis_config.categorization_field_name || null;
}

public async loadCategorizationFieldExamples() {
const { valid, examples } = await this._examplesLoader.loadExamples();
this._categoryFieldExamples = examples;
this._categoryFieldValid = valid;
return { valid, examples };
}

public get categoryFieldExamples() {
return this._categoryFieldExamples;
}

public get categoryFieldValid() {
return this._categoryFieldValid;
}

public get selectedDetectorType() {
return this._detectorType;
}

public set categorizationAnalyzer(analyzer: CategorizationAnalyzerType) {
this._categorizationAnalyzer = analyzer;

if (
analyzer === null ||
isEqual(this._categorizationAnalyzer, this._defaultCategorizationAnalyzer)
) {
delete this._job_config.analysis_config.categorization_analyzer;
} else {
this._job_config.analysis_config.categorization_analyzer = analyzer;
}
}

public get categorizationAnalyzer() {
return this._categorizationAnalyzer;
}

public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.CATEGORIZATION;
const detectors = getRichDetectors(job, datafeed, this.scriptFields, false);

const dtr = detectors[0];
if (detectors.length && dtr.agg !== null && dtr.field !== null) {
this._detectorType =
dtr.agg.id === ML_JOB_AGGREGATION.COUNT
? ML_JOB_AGGREGATION.COUNT
: ML_JOB_AGGREGATION.RARE;

const bs = job.analysis_config.bucket_span;
this.setDetectorType(this._detectorType);
// set the bucketspan back to the original value
// as setDetectorType applies a default
this.bucketSpan = bs;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export { SingleMetricJobCreator } from './single_metric_job_creator';
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 {
JobCreatorType,
isSingleMetricJobCreator,
isMultiMetricJobCreator,
isPopulationJobCreator,
isAdvancedJobCreator,
isCategorizationJobCreator,
} from './type_guards';
export { jobCreatorFactory } from './job_creator_factory';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MultiMetricJobCreator } from './multi_metric_job_creator';
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 { JOB_TYPE } from '../../../../../../common/constants/new_job';

Expand All @@ -32,6 +33,9 @@ export const jobCreatorFactory = (jobType: JOB_TYPE) => (
case JOB_TYPE.ADVANCED:
jc = AdvancedJobCreator;
break;
case JOB_TYPE.CATEGORIZATION:
jc = CategorizationJobCreator;
break;
default:
jc = SingleMetricJobCreator;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { SingleMetricJobCreator } from './single_metric_job_creator';
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 { JOB_TYPE } from '../../../../../../common/constants/new_job';

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

export function isSingleMetricJobCreator(
jobCreator: JobCreatorType
Expand All @@ -37,3 +39,9 @@ export function isPopulationJobCreator(
export function isAdvancedJobCreator(jobCreator: JobCreatorType): jobCreator is AdvancedJobCreator {
return jobCreator.type === JOB_TYPE.ADVANCED;
}

export function isCategorizationJobCreator(
jobCreator: JobCreatorType
): jobCreator is CategorizationJobCreator {
return jobCreator.type === JOB_TYPE.CATEGORIZATION;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import {
mlCategory,
} from '../../../../../../../common/types/fields';
import { mlJobService } from '../../../../../services/job_service';
import { JobCreatorType, isMultiMetricJobCreator, isPopulationJobCreator } from '../index';
import {
JobCreatorType,
isMultiMetricJobCreator,
isPopulationJobCreator,
isCategorizationJobCreator,
} from '../index';
import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../../common/constants/new_job';

const getFieldByIdFactory = (scriptFields: Field[]) => (id: string) => {
Expand Down Expand Up @@ -251,6 +256,8 @@ export function convertToAdvancedJob(jobCreator: JobCreatorType) {
jobType = JOB_TYPE.MULTI_METRIC;
} else if (isPopulationJobCreator(jobCreator)) {
jobType = JOB_TYPE.POPULATION;
} else if (isCategorizationJobCreator(jobCreator)) {
jobType = JOB_TYPE.CATEGORIZATION;
}

window.location.href = window.location.href.replace(jobType, JOB_TYPE.ADVANCED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import {
basicDatafeedValidation,
} from '../../../../../../common/util/job_utils';
import { getNewJobLimits } from '../../../../services/ml_server_info';
import { JobCreator, JobCreatorType } from '../job_creator';
import { JobCreator, JobCreatorType, isCategorizationJobCreator } from '../job_creator';
import { populateValidationMessages, checkForExistingJobAndGroupIds } from './util';
import { ExistingJobsAndGroups } from '../../../../services/job_service';
import { cardinalityValidator, CardinalityValidatorResult } from './validators';
import { CATEGORY_EXAMPLES_ERROR_LIMIT } from '../../../../../../common/constants/new_job';

// delay start of validation to allow the user to make changes
// e.g. if they are typing in a new value, try not to validate
Expand Down Expand Up @@ -51,6 +52,10 @@ export interface BasicValidations {
scrollSize: Validation;
}

export interface AdvancedValidations {
categorizationFieldValid: Validation;
}

export class JobValidator {
private _jobCreator: JobCreatorType;
private _validationSummary: ValidationSummary;
Expand All @@ -71,6 +76,9 @@ export class JobValidator {
frequency: { valid: true },
scrollSize: { valid: true },
};
private _advancedValidations: AdvancedValidations = {
categorizationFieldValid: { valid: true },
};
private _validating: boolean = false;
private _basicValidationResult$ = new ReplaySubject<JobValidationResult>(2);

Expand Down Expand Up @@ -141,6 +149,7 @@ export class JobValidator {
this._lastDatafeedConfig = formattedDatafeedConfig;
this._validateTimeout = setTimeout(() => {
this._runBasicValidation();
this._runAdvancedValidation();

this._jobCreatorSubject$.next(this._jobCreator);

Expand Down Expand Up @@ -195,6 +204,13 @@ export class JobValidator {
this._basicValidationResult$.next(this._basicValidations);
}

private _runAdvancedValidation() {
if (isCategorizationJobCreator(this._jobCreator)) {
this._advancedValidations.categorizationFieldValid.valid =
this._jobCreator.categoryFieldValid > CATEGORY_EXAMPLES_ERROR_LIMIT;
}
}

private _isOverallBasicValid() {
return Object.values(this._basicValidations).some(v => v.valid === false) === false;
}
Expand Down Expand Up @@ -246,4 +262,12 @@ export class JobValidator {
public get validating(): boolean {
return this._validating;
}

public get categorizationField() {
return this._advancedValidations.categorizationFieldValid.valid;
}

public set categorizationField(valid: boolean) {
this._advancedValidations.categorizationFieldValid.valid = valid;
}
}
Loading