mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[ML] translate anomaly utils (#28675)
[ML] translate anomalies table utils
This commit is contained in:
parent
890a094901
commit
9d72276f13
7 changed files with 92 additions and 50 deletions
|
@ -182,30 +182,30 @@ describe('ML - anomaly utils', () => {
|
|||
describe('getSeverity', () => {
|
||||
|
||||
it('returns warning for 0 <= score < 25', () => {
|
||||
expect(getSeverity(0)).to.be('warning');
|
||||
expect(getSeverity(0.001)).to.be('warning');
|
||||
expect(getSeverity(24.99)).to.be('warning');
|
||||
expect(getSeverity(0).id).to.be('warning');
|
||||
expect(getSeverity(0.001).id).to.be('warning');
|
||||
expect(getSeverity(24.99).id).to.be('warning');
|
||||
});
|
||||
|
||||
it('returns minor for 25 <= score < 50', () => {
|
||||
expect(getSeverity(25)).to.be('minor');
|
||||
expect(getSeverity(49.99)).to.be('minor');
|
||||
expect(getSeverity(25).id).to.be('minor');
|
||||
expect(getSeverity(49.99).id).to.be('minor');
|
||||
});
|
||||
|
||||
it('returns minor for 50 <= score < 75', () => {
|
||||
expect(getSeverity(50)).to.be('major');
|
||||
expect(getSeverity(74.99)).to.be('major');
|
||||
expect(getSeverity(50).id).to.be('major');
|
||||
expect(getSeverity(74.99).id).to.be('major');
|
||||
});
|
||||
|
||||
it('returns critical for score >= 75', () => {
|
||||
expect(getSeverity(75)).to.be('critical');
|
||||
expect(getSeverity(100)).to.be('critical');
|
||||
expect(getSeverity(1000)).to.be('critical');
|
||||
expect(getSeverity(75).id).to.be('critical');
|
||||
expect(getSeverity(100).id).to.be('critical');
|
||||
expect(getSeverity(1000).id).to.be('critical');
|
||||
});
|
||||
|
||||
it('returns unknown for scores less than 0 or string input', () => {
|
||||
expect(getSeverity(-10)).to.be('unknown');
|
||||
expect(getSeverity('value')).to.be('unknown');
|
||||
expect(getSeverity(-10).id).to.be('unknown');
|
||||
expect(getSeverity('value').id).to.be('unknown');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -213,35 +213,35 @@ describe('ML - anomaly utils', () => {
|
|||
describe('getSeverityWithLow', () => {
|
||||
|
||||
it('returns low for 0 <= score < 3', () => {
|
||||
expect(getSeverityWithLow(0)).to.be('low');
|
||||
expect(getSeverityWithLow(0.001)).to.be('low');
|
||||
expect(getSeverityWithLow(2.99)).to.be('low');
|
||||
expect(getSeverityWithLow(0).id).to.be('low');
|
||||
expect(getSeverityWithLow(0.001).id).to.be('low');
|
||||
expect(getSeverityWithLow(2.99).id).to.be('low');
|
||||
});
|
||||
|
||||
it('returns warning for 3 <= score < 25', () => {
|
||||
expect(getSeverityWithLow(3)).to.be('warning');
|
||||
expect(getSeverityWithLow(24.99)).to.be('warning');
|
||||
expect(getSeverityWithLow(3).id).to.be('warning');
|
||||
expect(getSeverityWithLow(24.99).id).to.be('warning');
|
||||
});
|
||||
|
||||
it('returns minor for 25 <= score < 50', () => {
|
||||
expect(getSeverityWithLow(25)).to.be('minor');
|
||||
expect(getSeverityWithLow(49.99)).to.be('minor');
|
||||
expect(getSeverityWithLow(25).id).to.be('minor');
|
||||
expect(getSeverityWithLow(49.99).id).to.be('minor');
|
||||
});
|
||||
|
||||
it('returns minor for 50 <= score < 75', () => {
|
||||
expect(getSeverityWithLow(50)).to.be('major');
|
||||
expect(getSeverityWithLow(74.99)).to.be('major');
|
||||
expect(getSeverityWithLow(50).id).to.be('major');
|
||||
expect(getSeverityWithLow(74.99).id).to.be('major');
|
||||
});
|
||||
|
||||
it('returns critical for score >= 75', () => {
|
||||
expect(getSeverityWithLow(75)).to.be('critical');
|
||||
expect(getSeverityWithLow(100)).to.be('critical');
|
||||
expect(getSeverityWithLow(1000)).to.be('critical');
|
||||
expect(getSeverityWithLow(75).id).to.be('critical');
|
||||
expect(getSeverityWithLow(100).id).to.be('critical');
|
||||
expect(getSeverityWithLow(1000).id).to.be('critical');
|
||||
});
|
||||
|
||||
it('returns unknown for scores less than 0 or string input', () => {
|
||||
expect(getSeverityWithLow(-10)).to.be('unknown');
|
||||
expect(getSeverityWithLow('value')).to.be('unknown');
|
||||
expect(getSeverityWithLow(-10).id).to.be('unknown');
|
||||
expect(getSeverityWithLow('value').id).to.be('unknown');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from '../constants/detector_rule';
|
||||
import { MULTI_BUCKET_IMPACT } from '../constants/multi_bucket_impact';
|
||||
|
||||
|
@ -23,19 +24,50 @@ const DISPLAY_ACTUAL_FUNCTIONS = ['count', 'distinct_count', 'lat_long', 'mean',
|
|||
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: 'critical', label: i18n.translate('xpack.ml.anomalyUtils.severity.criticalLabel', {
|
||||
defaultMessage: 'critical',
|
||||
}) },
|
||||
major: { id: 'major', label: i18n.translate('xpack.ml.anomalyUtils.severity.majorLabel', {
|
||||
defaultMessage: 'major',
|
||||
}) },
|
||||
minor: { id: 'minor', label: i18n.translate('xpack.ml.anomalyUtils.severity.minorLabel', {
|
||||
defaultMessage: 'minor',
|
||||
}) },
|
||||
warning: { id: 'warning', label: i18n.translate('xpack.ml.anomalyUtils.severity.warningLabel', {
|
||||
defaultMessage: 'warning',
|
||||
}) },
|
||||
unknown: { id: 'unknown', label: i18n.translate('xpack.ml.anomalyUtils.severity.unknownLabel', {
|
||||
defaultMessage: 'unknown',
|
||||
}) },
|
||||
low: { id: '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 >= 75) {
|
||||
return 'critical';
|
||||
return severityTypesList.critical;
|
||||
} else if (normalizedScore >= 50) {
|
||||
return 'major';
|
||||
return severityTypesList.major;
|
||||
} else if (normalizedScore >= 25) {
|
||||
return 'minor';
|
||||
return severityTypesList.minor;
|
||||
} else if (normalizedScore >= 0) {
|
||||
return 'warning';
|
||||
return severityTypesList.warning;
|
||||
} else {
|
||||
return 'unknown';
|
||||
return severityTypesList.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,18 +75,20 @@ export function getSeverity(normalizedScore) {
|
|||
// 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 >= 75) {
|
||||
return 'critical';
|
||||
return severityTypesList.critical;
|
||||
} else if (normalizedScore >= 50) {
|
||||
return 'major';
|
||||
return severityTypesList.major;
|
||||
} else if (normalizedScore >= 25) {
|
||||
return 'minor';
|
||||
return severityTypesList.minor;
|
||||
} else if (normalizedScore >= 3) {
|
||||
return 'warning';
|
||||
return severityTypesList.warning;
|
||||
} else if (normalizedScore >= 0) {
|
||||
return 'low';
|
||||
return severityTypesList.low;
|
||||
} else {
|
||||
return 'unknown';
|
||||
return severityTypesList.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,13 +115,21 @@ export function getSeverityColor(normalizedScore) {
|
|||
// which ranges from -5 to +5.
|
||||
export function getMultiBucketImpactLabel(multiBucketImpact) {
|
||||
if (multiBucketImpact >= MULTI_BUCKET_IMPACT.HIGH) {
|
||||
return 'high';
|
||||
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.highLabel', {
|
||||
defaultMessage: 'high',
|
||||
});
|
||||
} else if (multiBucketImpact >= MULTI_BUCKET_IMPACT.MEDIUM) {
|
||||
return 'medium';
|
||||
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.mediumLabel', {
|
||||
defaultMessage: 'medium',
|
||||
});
|
||||
} else if (multiBucketImpact >= MULTI_BUCKET_IMPACT.LOW) {
|
||||
return 'low';
|
||||
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.lowLabel', {
|
||||
defaultMessage: 'low',
|
||||
});
|
||||
} else {
|
||||
return 'none';
|
||||
return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.noneLabel', {
|
||||
defaultMessage: 'none',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ export class AnomalyDetails extends Component {
|
|||
let anomalyDescription = i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel', {
|
||||
defaultMessage: '{anomalySeverity} anomaly in {anomalyDetector}',
|
||||
values: {
|
||||
anomalySeverity: getSeverity(anomaly.severity),
|
||||
anomalySeverity: getSeverity(anomaly.severity).label,
|
||||
anomalyDetector: anomaly.detector,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -72,7 +72,7 @@ function Influencer({ influencerFieldName, valueData }) {
|
|||
<div className="field-value">mlcategory {valueData.influencerFieldValue}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={`progress ${severity}`} value="{valueData.maxAnomalyScore}" max="100">
|
||||
<div className={`progress ${severity.id}`} value="{valueData.maxAnomalyScore}" max="100">
|
||||
<div className="progress-bar-holder">
|
||||
<div className="progress-bar" style={barStyle}/>
|
||||
</div>
|
||||
|
|
|
@ -378,7 +378,7 @@ export const ExplorerChartDistribution = injectI18n(class ExplorerChartDistribut
|
|||
let markerClass = 'metric-value';
|
||||
if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= threshold.val) {
|
||||
markerClass += ' anomaly-marker ';
|
||||
markerClass += getSeverityWithLow(d.anomalyScore);
|
||||
markerClass += getSeverityWithLow(d.anomalyScore).id;
|
||||
}
|
||||
return markerClass;
|
||||
});
|
||||
|
|
|
@ -291,7 +291,7 @@ export const ExplorerChartSingleMetric = injectI18n(class ExplorerChartSingleMet
|
|||
.attr('class', (d) => {
|
||||
let markerClass = 'metric-value';
|
||||
if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= threshold.val) {
|
||||
markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore)}`;
|
||||
markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`;
|
||||
}
|
||||
return markerClass;
|
||||
});
|
||||
|
@ -307,7 +307,7 @@ export const ExplorerChartSingleMetric = injectI18n(class ExplorerChartSingleMet
|
|||
multiBucketMarkers.enter().append('path')
|
||||
.attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross'))
|
||||
.attr('transform', d => `translate(${lineChartXScale(d.date)}, ${lineChartYScale(d.value)})`)
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore)}`)
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}`)
|
||||
// Don't use an arrow function since we need access to `this`.
|
||||
.on('mouseover', function (d) {
|
||||
showLineChartTooltip(d, this);
|
||||
|
|
|
@ -778,7 +778,7 @@ export const TimeseriesChart = injectI18n(class TimeseriesChart extends React.Co
|
|||
.attr('class', (d) => {
|
||||
let markerClass = 'metric-value';
|
||||
if (_.has(d, 'anomalyScore')) {
|
||||
markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore)}`;
|
||||
markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`;
|
||||
}
|
||||
return markerClass;
|
||||
});
|
||||
|
@ -801,7 +801,7 @@ export const TimeseriesChart = injectI18n(class TimeseriesChart extends React.Co
|
|||
|
||||
// Update all markers to new positions.
|
||||
multiBucketMarkers.attr('transform', d => `translate(${this.focusXScale(d.date)}, ${this.focusYScale(d.value)})`)
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore)}`);
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}`);
|
||||
|
||||
|
||||
// Add rectangular markers for any scheduled events.
|
||||
|
@ -1522,13 +1522,13 @@ export const TimeseriesChart = injectI18n(class TimeseriesChart extends React.Co
|
|||
selectedMarker.enter().append('path')
|
||||
.attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross'))
|
||||
.attr('transform', d => `translate(${focusXScale(d.date)}, ${focusYScale(d.value)})`)
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore)} highlighted`);
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id} highlighted`);
|
||||
} else {
|
||||
selectedMarker.enter().append('circle')
|
||||
.attr('r', LINE_CHART_ANOMALY_RADIUS)
|
||||
.attr('cx', d => focusXScale(d.date))
|
||||
.attr('cy', d => focusYScale(d.value))
|
||||
.attr('class', d => `anomaly-marker metric-value ${getSeverityWithLow(d.anomalyScore)} highlighted`);
|
||||
.attr('class', d => `anomaly-marker metric-value ${getSeverityWithLow(d.anomalyScore).id} highlighted`);
|
||||
}
|
||||
|
||||
// Display the chart tooltip for this marker.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue