kibana/x-pack/plugins/ml/common/util/anomaly_utils.js
James Gowdy 35302ed273
[ML] Client side cut over (#60100)
* [ML] Client side cut over

* updating paths and commented code

* changes based on review

* disabling telemetry tests

* fixing start job stylesheets

* fixing everything that is broken

* fixing types and ml icon order

* using icon constant
2020-03-13 19:16:41 +00:00

332 lines
11 KiB
JavaScript

/*
* 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.
*/
/*
* Contains functions for operations commonly performed on anomaly data
* to extract information for display in dashboards.
*/
import { i18n } from '@kbn/i18n';
import { CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from '../constants/detector_rule';
import { MULTI_BUCKET_IMPACT } from '../constants/multi_bucket_impact';
import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from '../constants/anomalies';
// List of function descriptions for which actual values from record level results should be displayed.
const DISPLAY_ACTUAL_FUNCTIONS = [
'count',
'distinct_count',
'lat_long',
'mean',
'max',
'min',
'sum',
'median',
'varp',
'info_content',
'time',
];
// List of function descriptions for which typical values from record level results should be displayed.
const DISPLAY_TYPICAL_FUNCTIONS = [
'count',
'distinct_count',
'lat_long',
'mean',
'max',
'min',
'sum',
'median',
'varp',
'info_content',
'time',
];
let severityTypes;
function getSeverityTypes() {
if (severityTypes) {
return severityTypes;
}
return (severityTypes = {
critical: {
id: ANOMALY_SEVERITY.CRITICAL,
label: i18n.translate('xpack.ml.anomalyUtils.severity.criticalLabel', {
defaultMessage: 'critical',
}),
},
major: {
id: ANOMALY_SEVERITY.MAJOR,
label: i18n.translate('xpack.ml.anomalyUtils.severity.majorLabel', {
defaultMessage: 'major',
}),
},
minor: {
id: ANOMALY_SEVERITY.MINOR,
label: i18n.translate('xpack.ml.anomalyUtils.severity.minorLabel', {
defaultMessage: 'minor',
}),
},
warning: {
id: ANOMALY_SEVERITY.WARNING,
label: i18n.translate('xpack.ml.anomalyUtils.severity.warningLabel', {
defaultMessage: 'warning',
}),
},
unknown: {
id: ANOMALY_SEVERITY.UNKNOWN,
label: i18n.translate('xpack.ml.anomalyUtils.severity.unknownLabel', {
defaultMessage: 'unknown',
}),
},
low: {
id: ANOMALY_SEVERITY.LOW,
label: i18n.translate('xpack.ml.anomalyUtils.severityWithLow.lowLabel', {
defaultMessage: 'low',
}),
},
});
}
// Returns a severity label (one of critical, major, minor, warning or unknown)
// for the supplied normalized anomaly score (a value between 0 and 100).
export function getSeverity(normalizedScore) {
const severityTypesList = getSeverityTypes();
if (normalizedScore >= ANOMALY_THRESHOLD.CRITICAL) {
return severityTypesList.critical;
} else if (normalizedScore >= ANOMALY_THRESHOLD.MAJOR) {
return severityTypesList.major;
} else if (normalizedScore >= ANOMALY_THRESHOLD.MINOR) {
return severityTypesList.minor;
} else if (normalizedScore >= ANOMALY_THRESHOLD.LOW) {
return severityTypesList.warning;
} else {
return severityTypesList.unknown;
}
}
export function getSeverityType(normalizedScore) {
if (normalizedScore >= 75) {
return ANOMALY_SEVERITY.CRITICAL;
} else if (normalizedScore >= 50) {
return ANOMALY_SEVERITY.MAJOR;
} else if (normalizedScore >= 25) {
return ANOMALY_SEVERITY.MINOR;
} else if (normalizedScore >= 3) {
return ANOMALY_SEVERITY.WARNING;
} else if (normalizedScore >= 0) {
return ANOMALY_SEVERITY.LOW;
} else {
return ANOMALY_SEVERITY.UNKNOWN;
}
}
// Returns a severity label (one of critical, major, minor, warning, low or unknown)
// for the supplied normalized anomaly score (a value between 0 and 100), where scores
// less than 3 are assigned a severity of 'low'.
export function getSeverityWithLow(normalizedScore) {
const severityTypesList = getSeverityTypes();
if (normalizedScore >= ANOMALY_THRESHOLD.CRITICAL) {
return severityTypesList.critical;
} else if (normalizedScore >= ANOMALY_THRESHOLD.MAJOR) {
return severityTypesList.major;
} else if (normalizedScore >= ANOMALY_THRESHOLD.MINOR) {
return severityTypesList.minor;
} else if (normalizedScore >= ANOMALY_THRESHOLD.WARNING) {
return severityTypesList.warning;
} else if (normalizedScore >= ANOMALY_THRESHOLD.LOW) {
return severityTypesList.low;
} else {
return severityTypesList.unknown;
}
}
// Returns a severity RGB color (one of critical, major, minor, warning, low_warning or unknown)
// for the supplied normalized anomaly score (a value between 0 and 100).
export function getSeverityColor(normalizedScore) {
if (normalizedScore >= ANOMALY_THRESHOLD.CRITICAL) {
return '#fe5050';
} else if (normalizedScore >= ANOMALY_THRESHOLD.MAJOR) {
return '#fba740';
} else if (normalizedScore >= ANOMALY_THRESHOLD.MINOR) {
return '#fdec25';
} else if (normalizedScore >= ANOMALY_THRESHOLD.WARNING) {
return '#8bc8fb';
} else if (normalizedScore >= ANOMALY_THRESHOLD.LOW) {
return '#d2e9f7';
} else {
return '#ffffff';
}
}
// Returns a label to use for the multi-bucket impact of an anomaly
// according to the value of the multi_bucket_impact field of a record,
// which ranges from -5 to +5.
export function getMultiBucketImpactLabel(multiBucketImpact) {
if (multiBucketImpact >= MULTI_BUCKET_IMPACT.HIGH) {
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.highLabel', {
defaultMessage: 'high',
});
} else if (multiBucketImpact >= MULTI_BUCKET_IMPACT.MEDIUM) {
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.mediumLabel', {
defaultMessage: 'medium',
});
} else if (multiBucketImpact >= MULTI_BUCKET_IMPACT.LOW) {
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.lowLabel', {
defaultMessage: 'low',
});
} else {
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.noneLabel', {
defaultMessage: 'none',
});
}
}
// Returns the name of the field to use as the entity name from the source record
// obtained from Elasticsearch. The function looks first for a by_field, then over_field,
// then partition_field, returning undefined if none of these fields are present.
export function getEntityFieldName(record) {
// Analyses with by and over fields, will have a top-level by_field_name, but
// the by_field_value(s) will be in the nested causes array.
if (record.by_field_name !== undefined && record.by_field_value !== undefined) {
return record.by_field_name;
}
if (record.over_field_name !== undefined) {
return record.over_field_name;
}
if (record.partition_field_name !== undefined) {
return record.partition_field_name;
}
return undefined;
}
// Returns the value of the field to use as the entity value from the source record
// obtained from Elasticsearch. The function looks first for a by_field, then over_field,
// then partition_field, returning undefined if none of these fields are present.
export function getEntityFieldValue(record) {
if (record.by_field_value !== undefined) {
return record.by_field_value;
}
if (record.over_field_value !== undefined) {
return record.over_field_value;
}
if (record.partition_field_value !== undefined) {
return record.partition_field_value;
}
return undefined;
}
// Returns the list of partitioning entity fields for the source record as a list
// of objects in the form { fieldName: airline, fieldValue: AAL, fieldType: partition }
export function getEntityFieldList(record) {
const entityFields = [];
if (record.partition_field_name !== undefined) {
entityFields.push({
fieldName: record.partition_field_name,
fieldValue: record.partition_field_value,
fieldType: 'partition',
});
}
if (record.over_field_name !== undefined) {
entityFields.push({
fieldName: record.over_field_name,
fieldValue: record.over_field_value,
fieldType: 'over',
});
}
// For jobs with by and over fields, don't add the 'by' field as this
// field will only be added to the top-level fields for record type results
// if it also an influencer over the bucket.
if (record.by_field_name !== undefined && record.over_field_name === undefined) {
entityFields.push({
fieldName: record.by_field_name,
fieldValue: record.by_field_value,
fieldType: 'by',
});
}
return entityFields;
}
// Returns whether actual values should be displayed for a record with the specified function description.
// Note that the 'function' field in a record contains what the user entered e.g. 'high_count',
// whereas the 'function_description' field holds a ML-built display hint for function e.g. 'count'.
export function showActualForFunction(functionDescription) {
return DISPLAY_ACTUAL_FUNCTIONS.indexOf(functionDescription) > -1;
}
// Returns whether typical values should be displayed for a record with the specified function description.
// Note that the 'function' field in a record contains what the user entered e.g. 'high_count',
// whereas the 'function_description' field holds a ML-built display hint for function e.g. 'count'.
export function showTypicalForFunction(functionDescription) {
return DISPLAY_TYPICAL_FUNCTIONS.indexOf(functionDescription) > -1;
}
// Returns whether a rule can be configured against the specified anomaly.
export function isRuleSupported(record) {
// A rule can be configured with a numeric condition if the function supports it,
// and/or with scope if there is a partitioning fields.
return (
CONDITIONS_NOT_SUPPORTED_FUNCTIONS.indexOf(record.function) === -1 ||
getEntityFieldName(record) !== undefined
);
}
// Two functions for converting aggregation type names.
// ML and ES use different names for the same function.
// Possible values for ML aggregation type are (defined in lib/model/CAnomalyDetector.cc):
// count
// distinct_count
// rare
// info_content
// mean
// median
// min
// max
// varp
// sum
// lat_long
// time
// The input to toES and the output from toML correspond to the value of the
// function_description field of anomaly records.
export const aggregationTypeTransform = {
toES: function(oldAggType) {
let newAggType = oldAggType;
if (newAggType === 'mean') {
newAggType = 'avg';
} else if (newAggType === 'distinct_count') {
newAggType = 'cardinality';
} else if (newAggType === 'median') {
newAggType = 'percentiles';
}
return newAggType;
},
toML: function(oldAggType) {
let newAggType = oldAggType;
if (newAggType === 'avg') {
newAggType = 'mean';
} else if (newAggType === 'cardinality') {
newAggType = 'distinct_count';
} else if (newAggType === 'percentiles') {
newAggType = 'median';
}
return newAggType;
},
};