[ML] Anomaly Detection: Consolidate severity colors (#204803)

## Summary

Part of #140695.

We defined the hex codes for anomaly detection severity colors in
several places. This PR consolidates this and makes sure the hex codes
are defined in just one place. Note this PR doesn't change any of the
colors or styling related to anomaly detection, it is pure refactoring
to make future updates to these colors more easy.

- Renames `BLANK` to `UNKNOWN` to be in line with severity labels.
- Uses `ML_SEVERITY_COLORS.*` in test assertions so future color changes
won't need updating every assertion.
- Migrates all SCSS that made use of severity colors to emotion so it
can reuse `ML_SEVERITY_COLORS`. Therefor the SCSS based severity colors
get removed in this PR.

### Checklist

- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Walter Rafelsberger 2025-01-02 16:18:34 +01:00 committed by GitHub
parent 1be2c06389
commit da8f0127e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 222 additions and 248 deletions

View file

@ -6,36 +6,37 @@
*/
import { getSeverityColor } from './get_severity_color';
import { ML_SEVERITY_COLORS } from './severity_colors';
describe('getSeverityColor', () => {
test('returns correct hex code for low for 0 <= score < 3', () => {
expect(getSeverityColor(0)).toBe('#d2e9f7');
expect(getSeverityColor(0.001)).toBe('#d2e9f7');
expect(getSeverityColor(2.99)).toBe('#d2e9f7');
expect(getSeverityColor(0)).toBe(ML_SEVERITY_COLORS.LOW);
expect(getSeverityColor(0.001)).toBe(ML_SEVERITY_COLORS.LOW);
expect(getSeverityColor(2.99)).toBe(ML_SEVERITY_COLORS.LOW);
});
test('returns correct hex code for warning for 3 <= score < 25', () => {
expect(getSeverityColor(3)).toBe('#8bc8fb');
expect(getSeverityColor(24.99)).toBe('#8bc8fb');
expect(getSeverityColor(3)).toBe(ML_SEVERITY_COLORS.WARNING);
expect(getSeverityColor(24.99)).toBe(ML_SEVERITY_COLORS.WARNING);
});
test('returns correct hex code for minor for 25 <= score < 50', () => {
expect(getSeverityColor(25)).toBe('#fdec25');
expect(getSeverityColor(49.99)).toBe('#fdec25');
expect(getSeverityColor(25)).toBe(ML_SEVERITY_COLORS.MINOR);
expect(getSeverityColor(49.99)).toBe(ML_SEVERITY_COLORS.MINOR);
});
test('returns correct hex code for major for 50 <= score < 75', () => {
expect(getSeverityColor(50)).toBe('#fba740');
expect(getSeverityColor(74.99)).toBe('#fba740');
expect(getSeverityColor(50)).toBe(ML_SEVERITY_COLORS.MAJOR);
expect(getSeverityColor(74.99)).toBe(ML_SEVERITY_COLORS.MAJOR);
});
test('returns correct hex code for critical for score >= 75', () => {
expect(getSeverityColor(75)).toBe('#fe5050');
expect(getSeverityColor(100)).toBe('#fe5050');
expect(getSeverityColor(1000)).toBe('#fe5050');
expect(getSeverityColor(75)).toBe(ML_SEVERITY_COLORS.CRITICAL);
expect(getSeverityColor(100)).toBe(ML_SEVERITY_COLORS.CRITICAL);
expect(getSeverityColor(1000)).toBe(ML_SEVERITY_COLORS.CRITICAL);
});
test('returns correct hex code for unknown for scores less than 0', () => {
expect(getSeverityColor(-10)).toBe('#ffffff');
expect(getSeverityColor(-10)).toBe(ML_SEVERITY_COLORS.UNKNOWN);
});
});

View file

@ -9,7 +9,7 @@ import { ML_ANOMALY_THRESHOLD } from './anomaly_threshold';
import { ML_SEVERITY_COLORS } from './severity_colors';
/**
* Returns a severity RGB color (one of critical, major, minor, warning, low or blank)
* Returns a severity RGB color (one of critical, major, minor, warning, low or unknown)
* for the supplied normalized anomaly score (a value between 0 and 100).
* @param normalizedScore - A normalized score between 0-100, which is based on the probability of the anomalousness of this record
*/
@ -25,6 +25,6 @@ export function getSeverityColor(normalizedScore: number): string {
} else if (normalizedScore >= ML_ANOMALY_THRESHOLD.LOW) {
return ML_SEVERITY_COLORS.LOW;
} else {
return ML_SEVERITY_COLORS.BLANK;
return ML_SEVERITY_COLORS.UNKNOWN;
}
}

View file

@ -38,5 +38,5 @@ export const ML_SEVERITY_COLORS = {
/**
* Color used in the UI to indicate an anomaly for which the score is unknown.
*/
BLANK: '#ffffff',
UNKNOWN: '#ffffff',
};

View file

@ -5,11 +5,16 @@
* 2.0.
*/
import React from 'react';
import { EuiHealth } from '@elastic/eui';
import { type FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
import React from 'react';
import { ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils';
import { ALERT_ANOMALY_SCORE } from '../../../common/constants/alerts';
import { getAlertFormatters } from './render_cell_value';
describe('getAlertFormatters', () => {
@ -19,31 +24,31 @@ describe('getAlertFormatters', () => {
test('format anomaly score correctly', () => {
expect(alertFormatter(ALERT_ANOMALY_SCORE, 50.3)).toEqual(
<EuiHealth color="#fba740" textSize="xs">
<EuiHealth color={ML_SEVERITY_COLORS.MAJOR} textSize="xs">
50
</EuiHealth>
);
expect(alertFormatter(ALERT_ANOMALY_SCORE, '50.3,89.6')).toEqual(
<EuiHealth color="#fe5050" textSize="xs">
<EuiHealth color={ML_SEVERITY_COLORS.CRITICAL} textSize="xs">
89
</EuiHealth>
);
expect(alertFormatter(ALERT_ANOMALY_SCORE, '0.7')).toEqual(
<EuiHealth color="#d2e9f7" textSize="xs">
<EuiHealth color={ML_SEVERITY_COLORS.LOW} textSize="xs">
&lt; 1
</EuiHealth>
);
expect(alertFormatter(ALERT_ANOMALY_SCORE, '0')).toEqual(
<EuiHealth color="#d2e9f7" textSize="xs">
<EuiHealth color={ML_SEVERITY_COLORS.LOW} textSize="xs">
&lt; 1
</EuiHealth>
);
expect(alertFormatter(ALERT_ANOMALY_SCORE, '')).toEqual(
<EuiHealth color="#d2e9f7" textSize="xs">
<EuiHealth color={ML_SEVERITY_COLORS.LOW} textSize="xs">
&lt; 1
</EuiHealth>
);

View file

@ -1,6 +1,3 @@
// ML has it's own variables for coloring
@import 'variables';
// Protect the rest of Kibana from ML generic namespacing
// SASSTODO: Prefix ml selectors instead
.ml-app {
@ -14,4 +11,4 @@
@import 'components/job_selector/index';
@import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly
}
}

View file

@ -1,12 +0,0 @@
$mlColorCritical: #FE5050;
$mlColorMajor: #FBA740;
$mlColorMinor: #FDEC25;
$mlColorWarning: #8BC8FB;
$mlColorLowWarning: #D2E9F7;
$mlColorUnknown: #C0C0C0;
$mlColorCriticalText: makeHighContrastColor($mlColorCritical, $euiColorEmptyShade);
$mlColorMajorText: makeHighContrastColor($mlColorMajor, $euiColorEmptyShade);
$mlColorMinorText: makeHighContrastColor($mlColorMinor, $euiColorEmptyShade);
$mlColorWarningText: makeHighContrastColor($mlColorWarning, $euiColorEmptyShade);
$mlColorUnknownText: $euiColorDarkShade;

View file

@ -7,6 +7,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils/severity_colors';
import { SeverityCell } from './severity_cell';
describe('SeverityCell', () => {
@ -18,7 +19,7 @@ describe('SeverityCell', () => {
const { container } = render(<SeverityCell {...props} />);
expect(container.textContent).toBe('75');
const svgEl = container.querySelectorAll('[data-euiicon-type]')[0];
expect(svgEl && svgEl.getAttribute('color')).toBe('#fe5050');
expect(svgEl && svgEl.getAttribute('color')).toBe(ML_SEVERITY_COLORS.CRITICAL);
});
test('should render a multi-bucket marker with low severity score', () => {
@ -29,6 +30,6 @@ describe('SeverityCell', () => {
const { container } = render(<SeverityCell {...props} />);
expect(container.textContent).toBe('< 1');
const svgEl = container.getElementsByTagName('svg').item(0);
expect(svgEl && svgEl.getAttribute('fill')).toBe('#d2e9f7');
expect(svgEl && svgEl.getAttribute('fill')).toBe(ML_SEVERITY_COLORS.LOW);
});
});

View file

@ -9,7 +9,7 @@ import { css } from '@emotion/react';
import { useEuiFontSize, useEuiTheme } from '@elastic/eui';
import { mlColors } from '../../styles';
import { ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils';
export const useInfluencersListStyles = () => {
const { euiTheme } = useEuiTheme();
@ -50,12 +50,12 @@ export const useInfluencersListStyles = () => {
width: `${barScore}%`,
backgroundColor:
severity === 'critical'
? mlColors.critical
? ML_SEVERITY_COLORS.CRITICAL
: severity === 'major'
? mlColors.major
? ML_SEVERITY_COLORS.MAJOR
: severity === 'minor'
? mlColors.minor
: mlColors.warning,
? ML_SEVERITY_COLORS.MINOR
: ML_SEVERITY_COLORS.WARNING,
}),
scoreLabel: (severity: string) =>
css({
@ -67,12 +67,12 @@ export const useInfluencersListStyles = () => {
display: 'inline',
borderColor:
severity === 'critical'
? mlColors.critical
? ML_SEVERITY_COLORS.CRITICAL
: severity === 'major'
? mlColors.major
? ML_SEVERITY_COLORS.MAJOR
: severity === 'minor'
? mlColors.minor
: mlColors.warning,
? ML_SEVERITY_COLORS.MINOR
: ML_SEVERITY_COLORS.WARNING,
}),
totalScoreLabel: css({
width: euiTheme.size.xl,

View file

@ -10,7 +10,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import type { EuiRangeProps } from '@elastic/eui';
import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiRange } from '@elastic/eui';
import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils';
import { ML_ANOMALY_THRESHOLD, ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils';
export interface SeveritySelectorProps {
value: number | undefined;
@ -24,22 +24,22 @@ export const SeverityControl: FC<SeveritySelectorProps> = React.memo(({ value, o
{
min: ML_ANOMALY_THRESHOLD.LOW,
max: ML_ANOMALY_THRESHOLD.MINOR,
color: '#8BC8FB',
color: ML_SEVERITY_COLORS.WARNING,
},
{
min: ML_ANOMALY_THRESHOLD.MINOR,
max: ML_ANOMALY_THRESHOLD.MAJOR,
color: '#FDEC25',
color: ML_SEVERITY_COLORS.MINOR,
},
{
min: ML_ANOMALY_THRESHOLD.MAJOR,
max: ML_ANOMALY_THRESHOLD.CRITICAL,
color: '#FBA740',
color: ML_SEVERITY_COLORS.MAJOR,
},
{
min: ML_ANOMALY_THRESHOLD.CRITICAL,
max: MAX_ANOMALY_SCORE,
color: '#FE5050',
color: ML_SEVERITY_COLORS.CRITICAL,
},
];

View file

@ -1,120 +0,0 @@
// stylelint-disable selector-no-qualifying-type
.ml-explorer-chart-container {
overflow: hidden;
.ml-explorer-chart-svg {
font-size: $euiFontSizeXS;
font-family: $euiFontFamily;
.line-chart {
rect {
fill: $euiColorEmptyShade;
opacity: 1;
}
rect.selected-interval {
fill: rgba(200, 200, 200, .1);
stroke: $euiColorDarkShade;
stroke-width: calc($euiSizeXS / 2);
stroke-opacity: .8;
}
rect.scheduled-event-marker {
stroke-width: 1px;
stroke: $euiColorDarkShade;
fill: $euiColorLightShade;
pointer-events: none;
}
}
}
.axis path, .axis line {
fill: none;
stroke: $euiBorderColor;
shape-rendering: crispEdges;
}
.axis .tick line.ml-tick-emphasis {
stroke: rgba(0, 0, 0, .2);
}
.axis text {
fill: $euiColorDarkShade;
}
.axis .tick line {
stroke: $euiColorLightShade;
stroke-width: 1px;
}
.values-line {
fill: none;
stroke: $euiColorPrimary;
stroke-width: 2;
}
.values-dots circle,
.values-dots-circle {
fill: $euiColorPrimary;
stroke-width: 0;
}
.values-dots circle.values-dots-circle-blur {
fill: $euiColorMediumShade;
}
.metric-value {
opacity: 1;
fill: transparent;
stroke: $euiColorPrimary;
stroke-width: 0;
}
.anomaly-marker {
stroke-width: 1px;
stroke: $euiColorMediumShade;
}
.anomaly-marker:hover {
stroke-width: 6px;
stroke: $euiColorPrimary;
}
.anomaly-marker.critical {
fill: $mlColorCritical;
}
.anomaly-marker.major {
fill: $mlColorMajor;
}
.anomaly-marker.minor {
fill: $mlColorMinor;
}
.anomaly-marker.warning {
fill: $mlColorWarning;
}
.anomaly-marker.low {
fill: $mlColorLowWarning;
}
.metric-value:hover,
.anomaly-marker:hover {
stroke-width: 6px;
stroke-opacity: .65;
}
}
.ml-explorer-chart {
overflow: hidden;
}
.ml-explorer-chart-content-wrapper {
height: 215px;
}
.ml-explorer-chart-axis-emphasis {
font-weight: bold;
}

View file

@ -1,3 +1 @@
@import '../../../application/variables';
@import 'components/explorer_chart_label/index';
@import 'explorer_chart';

View file

@ -47,6 +47,7 @@ import { CHART_HEIGHT, TRANSPARENT_BACKGROUND } from './constants';
import { filter } from 'rxjs';
import { drawCursor } from './utils/draw_anomaly_explorer_charts_cursor';
import { SCHEDULE_EVENT_MARKER_ENTITY } from '../../../../common/constants/charts';
import { cssMlExplorerChart } from './explorer_chart_styles';
const popoverMenuOffset = 0;
const CONTENT_WRAPPER_HEIGHT = 215;
@ -776,7 +777,7 @@ export class ExplorerChartDistribution extends React.Component {
</EuiPopover>
</div>
)}
<div className="ml-explorer-chart" ref={this.setRef.bind(this)}>
<div css={cssMlExplorerChart} ref={this.setRef.bind(this)}>
{isLoading && <LoadingIndicator height={CONTENT_WRAPPER_HEIGHT} />}
{!isLoading && <div className="content-wrapper" />}
</div>

View file

@ -12,6 +12,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import { css } from '@emotion/react';
import d3 from 'd3';
import moment from 'moment';
@ -48,10 +49,15 @@ import { LoadingIndicator } from '../../components/loading_indicator/loading_ind
import { CHART_HEIGHT, TRANSPARENT_BACKGROUND } from './constants';
import { filter } from 'rxjs';
import { drawCursor } from './utils/draw_anomaly_explorer_charts_cursor';
import { cssMlExplorerChart } from './explorer_chart_styles';
const popoverMenuOffset = 0;
const CONTENT_WRAPPER_HEIGHT = 215;
// Not used for CSS, but with d3 to select elements.
const CONTENT_WRAPPER_CLASS = 'ml-explorer-chart-content-wrapper';
const mlExplorerChartContentWrapper = css({
height: `${CONTENT_WRAPPER_HEIGHT}px`,
});
export class ExplorerChartSingleMetric extends React.Component {
static contextType = context;
@ -721,9 +727,11 @@ export class ExplorerChartSingleMetric extends React.Component {
</EuiPopover>
</div>
)}
<div className="ml-explorer-chart" ref={this.setRef.bind(this)}>
<div css={cssMlExplorerChart} ref={this.setRef.bind(this)}>
{isLoading && <LoadingIndicator height={CONTENT_WRAPPER_HEIGHT} />}
{!isLoading && <div className={CONTENT_WRAPPER_CLASS} />}
{!isLoading && (
<div className={CONTENT_WRAPPER_CLASS} css={mlExplorerChartContentWrapper} />
)}
</div>
</>
);

View file

@ -0,0 +1,131 @@
/*
* 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 { css } from '@emotion/react';
import { useEuiFontSize, useEuiTheme, mathWithUnits, transparentize } from '@elastic/eui';
import { ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils';
export const useCssMlExplorerChartContainer = () => {
const { euiTheme } = useEuiTheme();
const euiFontSizeXS = useEuiFontSize('xs').fontSize;
return css({
overflow: 'hidden',
'.ml-explorer-chart-svg': {
fontSize: euiFontSizeXS,
fontFamily: euiTheme.font.family,
'.line-chart': {
rect: {
fill: euiTheme.colors.emptyShade,
opacity: 1,
},
'rect.selected-interval': {
fill: transparentize('#c8c8c8', 0.1),
stroke: euiTheme.colors.darkShade,
strokeWidth: mathWithUnits(euiTheme.size.xs, (x) => x / 2),
strokeOpacity: 0.8,
},
'rect.scheduled-event-marker': {
strokeWidth: '1px',
stroke: euiTheme.colors.darkShade,
fill: euiTheme.colors.lightShade,
pointerEvents: 'none',
},
},
},
'.axis path, .axis line': {
fill: 'none',
stroke: euiTheme.border.color,
shapeRendering: 'crispEdges',
},
'.axis .tick line.ml-tick-emphasis': {
stroke: transparentize('#000', 0.2),
},
'.axis text': {
fill: euiTheme.colors.lightShade,
},
'.axis .tick line': {
stroke: euiTheme.colors.lightShade,
strokeWidth: '1px',
},
'.values-line': {
fill: 'none',
stroke: euiTheme.colors.primary,
strokeWidth: '2px',
},
'.values-dots circle, .values-dots-circle': {
fill: euiTheme.colors.primary,
strokeWidth: 0,
},
'.values-dots circle.values-dots-circle-blur': {
fill: euiTheme.colors.mediumShade,
},
'.metric-value': {
opacity: 1,
fill: 'transparent',
stroke: euiTheme.colors.primary,
strokeWidth: 0,
},
'.anomaly-marker': {
strokeWidth: '1px',
stroke: euiTheme.colors.mediumShade,
},
'.anomaly-marker:hover': {
strokeWidth: '6px',
stroke: euiTheme.colors.primary,
},
'.anomaly-marker.critical': {
fill: ML_SEVERITY_COLORS.CRITICAL,
},
'.anomaly-marker.major': {
fill: ML_SEVERITY_COLORS.MAJOR,
},
'.anomaly-marker.minor': {
fill: ML_SEVERITY_COLORS.MINOR,
},
'.anomaly-marker.warning': {
fill: ML_SEVERITY_COLORS.WARNING,
},
'.anomaly-marker.low': {
fill: ML_SEVERITY_COLORS.LOW,
},
'.metric-value:hover, .anomaly-marker:hover': {
strokeWidth: '6px',
strokeOpacity: 0.65,
},
'ml-explorer-chart-axis-emphasis': {
fontWeight: 'bold',
},
});
};
export const cssMlExplorerChart = css({
overflow: 'hidden',
});

View file

@ -45,6 +45,7 @@ import { EmbeddedMapComponentWrapper } from './explorer_chart_embedded_map';
import { useActiveCursor } from '@kbn/charts-plugin/public';
import { BarSeries, Chart, Settings, LEGACY_LIGHT_THEME } from '@elastic/charts';
import { escapeKueryForFieldValuePair } from '../../util/string_utils';
import { useCssMlExplorerChartContainer } from './explorer_chart_styles';
const textTooManyBuckets = i18n.translate('xpack.ml.explorer.charts.tooManyBucketsDescription', {
defaultMessage:
@ -398,6 +399,7 @@ export const ExplorerChartsContainerUI = ({
chartsService,
showFilterIcons = true,
}) => {
const cssMlExplorerChartContainer = useCssMlExplorerChartContainer();
const {
services: { embeddable: embeddablePlugin, maps: mapsPlugin },
} = kibana;
@ -443,7 +445,8 @@ export const ExplorerChartsContainerUI = ({
return (
<EuiFlexItem
key={chartId}
className="ml-explorer-chart-container"
data-test-subj="mlExplorerChartContainerItem"
css={cssMlExplorerChartContainer}
style={{ minWidth: chartsWidth }}
>
<ExplorerChartContainer

View file

@ -119,7 +119,9 @@ describe('ExplorerChartsContainer', () => {
// We test child components with snapshots separately
// so we just do a high level check here.
expect(wrapper.find('div.ml-explorer-chart-container').children()).toHaveLength(1);
expect(
wrapper.find('div[data-test-subj="mlExplorerChartContainerItem"]').children()
).toHaveLength(1);
// Check if the additional y-axis information for rare charts is not part of the chart
expect(wrapper.html().search(rareChartUniqueString)).toBe(-1);
@ -148,7 +150,9 @@ describe('ExplorerChartsContainer', () => {
// We test child components with snapshots separately
// so we just do a high level check here.
expect(wrapper.find('div.ml-explorer-chart-container').children()).toHaveLength(1);
expect(
wrapper.find('div[data-test-subj="mlExplorerChartContainerItem"]').children()
).toHaveLength(1);
// Check if the additional y-axis information for rare charts is part of the chart
expect(wrapper.html().search(rareChartUniqueString)).toBeGreaterThan(0);

View file

@ -20,7 +20,11 @@ import moment from 'moment';
import { EuiPopover } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getFormattedSeverityScore, getSeverityWithLow } from '@kbn/ml-anomaly-utils';
import {
getFormattedSeverityScore,
getSeverityWithLow,
ML_SEVERITY_COLORS,
} from '@kbn/ml-anomaly-utils';
import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils';
import { context } from '@kbn/kibana-react-plugin/public';
@ -87,7 +91,13 @@ const ZOOM_INTERVAL_OPTIONS = [
const anomalyColorScale = d3.scale
.threshold()
.domain([3, 25, 50, 75, 100])
.range(['#d2e9f7', '#8bc8fb', '#ffdd00', '#ff7e00', '#fe5050']);
.range([
ML_SEVERITY_COLORS.LOW,
ML_SEVERITY_COLORS.WARNING,
ML_SEVERITY_COLORS.MINOR,
ML_SEVERITY_COLORS.MAJOR,
ML_SEVERITY_COLORS.CRITICAL,
]);
// Create a gray-toned version of the color scale to use under the context chart mask.
const anomalyGrayScale = d3.scale

View file

@ -10,7 +10,7 @@ import { css } from '@emotion/react';
import { useEuiFontSize, useEuiTheme, transparentize } from '@elastic/eui';
import { mlColors } from '../styles';
import { ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils';
// Annotations constants
const mlAnnotationBorderWidth = '2px';
@ -119,23 +119,23 @@ export const useTimeseriesExplorerStyles = () => {
stroke: euiTheme.colors.mediumShade,
'&.critical': {
fill: mlColors.critical,
fill: ML_SEVERITY_COLORS.CRITICAL,
},
'&.major': {
fill: mlColors.major,
fill: ML_SEVERITY_COLORS.MAJOR,
},
'&.minor': {
fill: mlColors.minor,
fill: ML_SEVERITY_COLORS.MINOR,
},
'&.warning': {
fill: mlColors.warning,
fill: ML_SEVERITY_COLORS.WARNING,
},
'&.low': {
fill: mlColors.lowWarning,
fill: ML_SEVERITY_COLORS.LOW,
},
},

View file

@ -11,7 +11,7 @@ import styled from 'styled-components';
import type { DescriptionList } from '../../../../../common/utility_types';
import type { Anomaly, NarrowDateRange } from '../types';
import { getScoreString } from './score_health';
import { getScoreString } from './get_score_string';
import { PreferenceFormattedDate } from '../../formatted_date';
import { createInfluencers } from '../influencers/create_influencers';
import * as i18n from './translations';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { getScoreString } from './score_health';
import { getScoreString } from './get_score_string';
describe('create_influencers', () => {
test('it rounds up to 1 from 0.3', () => {

View file

@ -5,13 +5,4 @@
* 2.0.
*/
// Replacement for ./_variables.scss as we aim to remove the scss files
export const mlColors = {
critical: '#FE5050',
major: '#FBA740',
minor: '#FDEC25',
warning: '#8BC8FB',
lowWarning: '#D2E9F7',
unknown: '#C0C0C0',
};
export const getScoreString = (score: number) => String(Math.ceil(score));

View file

@ -13,7 +13,7 @@ import {
} from '../../cell_actions';
import type { Anomaly } from '../types';
import { Spacer } from '../../page';
import { getScoreString } from './score_health';
import { getScoreString } from './get_score_string';
export const ScoreComponent = ({
index = 0,

View file

@ -1,44 +0,0 @@
/*
* 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 React from 'react';
import { EuiHealth } from '@elastic/eui';
interface Props {
score: number;
}
export const getScoreString = (score: number) => String(Math.ceil(score));
export const ScoreHealth = React.memo<Props>(({ score }) => {
const scoreCeiling = getScoreString(score);
const color = getSeverityColor(score);
return <EuiHealth color={color}>{scoreCeiling}</EuiHealth>;
});
ScoreHealth.displayName = 'ScoreHealth';
// ಠ_ಠ A hard-fork of the @kbn/ml-anomaly-utils;#getSeverityColor ಠ_ಠ
//
// 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 const getSeverityColor = (normalizedScore: number): string => {
if (normalizedScore >= 75) {
return '#fe5050';
} else if (normalizedScore >= 50) {
return '#fba740';
} else if (normalizedScore >= 25) {
return '#fdec25';
} else if (normalizedScore >= 3) {
return '#8bc8fb';
} else if (normalizedScore >= 0) {
return '#d2e9f7';
} else {
return '#ffffff';
}
};

View file

@ -31,7 +31,7 @@ export function AnomalyChartsProvider({ getService }: FtrProviderContext) {
? await testSubjects.find(chartsContainerSubj)
: await testSubjects.find('mlExplorerChartsContainer');
const actualChartsCount = (
await chartsContainer?.findAllByClassName('ml-explorer-chart-container', 3000)
await chartsContainer?.findAllByTestSubject('mlExplorerChartContainerItem', 3000)
).length;
expect(actualChartsCount).to.eql(
expectedChartsCount,