mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
1be2c06389
commit
da8f0127e1
24 changed files with 222 additions and 248 deletions
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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">
|
||||
< 1
|
||||
</EuiHealth>
|
||||
);
|
||||
|
||||
expect(alertFormatter(ALERT_ANOMALY_SCORE, '0')).toEqual(
|
||||
<EuiHealth color="#d2e9f7" textSize="xs">
|
||||
<EuiHealth color={ML_SEVERITY_COLORS.LOW} textSize="xs">
|
||||
< 1
|
||||
</EuiHealth>
|
||||
);
|
||||
|
||||
expect(alertFormatter(ALERT_ANOMALY_SCORE, '')).toEqual(
|
||||
<EuiHealth color="#d2e9f7" textSize="xs">
|
||||
<EuiHealth color={ML_SEVERITY_COLORS.LOW} textSize="xs">
|
||||
< 1
|
||||
</EuiHealth>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,3 +1 @@
|
|||
@import '../../../application/variables';
|
||||
@import 'components/explorer_chart_label/index';
|
||||
@import 'explorer_chart';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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));
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
};
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue