From 0de32a018d08cd85c452d4d222bd499a97525445 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 2 Jan 2025 16:18:34 +0100 Subject: [PATCH] [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) --- .../anomaly_utils/get_severity_color.test.ts | 27 ++-- .../ml/anomaly_utils/get_severity_color.ts | 4 +- .../ml/anomaly_utils/severity_colors.ts | 2 +- .../render_cell_value.test.tsx | 17 ++- .../shared/ml/public/application/_index.scss | 5 +- .../ml/public/application/_variables.scss | 12 -- .../severity_cell/severity_cell.test.tsx | 5 +- .../influencers_list_styles.ts | 18 +-- .../severity_control/severity_control.tsx | 10 +- .../explorer_charts/_explorer_chart.scss | 120 ---------------- .../explorer/explorer_charts/_index.scss | 2 - .../explorer_chart_distribution.js | 3 +- .../explorer_chart_single_metric.js | 12 +- .../explorer_charts/explorer_chart_styles.ts | 131 ++++++++++++++++++ .../explorer_charts_container.js | 5 +- .../explorer_charts_container.test.js | 8 +- .../timeseries_chart/timeseries_chart.js | 14 +- .../application/timeseriesexplorer/styles.ts | 12 +- .../ml/score/create_description_list.tsx | 2 +- .../ml/score/get_score_string.test.ts | 2 +- .../components/ml/score/get_score_string.ts} | 11 +- .../common/components/ml/score/score.tsx | 2 +- .../components/ml/score/score_health.tsx | 44 ------ .../functional/services/ml/anomaly_charts.ts | 2 +- 24 files changed, 222 insertions(+), 248 deletions(-) delete mode 100644 x-pack/platform/plugins/shared/ml/public/application/_variables.scss delete mode 100644 x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_explorer_chart.scss create mode 100644 x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_styles.ts rename x-pack/{platform/plugins/shared/ml/public/application/styles.ts => solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.ts} (52%) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score_health.tsx diff --git a/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.test.ts b/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.test.ts index 64bf7a5284c89..e91142934359d 100644 --- a/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.test.ts +++ b/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.test.ts @@ -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); }); }); diff --git a/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.ts b/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.ts index f17609ecef5e7..dcc5ac3544e1b 100644 --- a/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.ts +++ b/x-pack/platform/packages/shared/ml/anomaly_utils/get_severity_color.ts @@ -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; } } diff --git a/x-pack/platform/packages/shared/ml/anomaly_utils/severity_colors.ts b/x-pack/platform/packages/shared/ml/anomaly_utils/severity_colors.ts index e871528463399..67cc59fe46a2b 100644 --- a/x-pack/platform/packages/shared/ml/anomaly_utils/severity_colors.ts +++ b/x-pack/platform/packages/shared/ml/anomaly_utils/severity_colors.ts @@ -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', }; diff --git a/x-pack/platform/plugins/shared/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.test.tsx b/x-pack/platform/plugins/shared/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.test.tsx index bba6b909863bb..5df6f4c018ffe 100644 --- a/x-pack/platform/plugins/shared/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.test.tsx +++ b/x-pack/platform/plugins/shared/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.test.tsx @@ -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( - + 50 ); expect(alertFormatter(ALERT_ANOMALY_SCORE, '50.3,89.6')).toEqual( - + 89 ); expect(alertFormatter(ALERT_ANOMALY_SCORE, '0.7')).toEqual( - + < 1 ); expect(alertFormatter(ALERT_ANOMALY_SCORE, '0')).toEqual( - + < 1 ); expect(alertFormatter(ALERT_ANOMALY_SCORE, '')).toEqual( - + < 1 ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/_index.scss b/x-pack/platform/plugins/shared/ml/public/application/_index.scss index 91201434b20b1..66b04a8ff0e78 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/_index.scss +++ b/x-pack/platform/plugins/shared/ml/public/application/_index.scss @@ -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 -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/shared/ml/public/application/_variables.scss b/x-pack/platform/plugins/shared/ml/public/application/_variables.scss deleted file mode 100644 index f81a97cf8c156..0000000000000 --- a/x-pack/platform/plugins/shared/ml/public/application/_variables.scss +++ /dev/null @@ -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; diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx index e9166c0c6c28c..25071f30400cb 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx @@ -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(); 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(); 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); }); }); diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/influencers_list/influencers_list_styles.ts b/x-pack/platform/plugins/shared/ml/public/application/components/influencers_list/influencers_list_styles.ts index d54eb495a0a33..e6cfd5b3f66fa 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/influencers_list/influencers_list_styles.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/components/influencers_list/influencers_list_styles.ts @@ -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, diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/severity_control/severity_control.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/severity_control/severity_control.tsx index 1392c38d84507..096b7d263b183 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/severity_control/severity_control.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/severity_control/severity_control.tsx @@ -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 = 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, }, ]; diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_explorer_chart.scss b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_explorer_chart.scss deleted file mode 100644 index 29967e8db9b3f..0000000000000 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_explorer_chart.scss +++ /dev/null @@ -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; -} diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_index.scss b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_index.scss index d32d1ce81cd78..62ef4c05e1f7a 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_index.scss +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/_index.scss @@ -1,3 +1 @@ -@import '../../../application/variables'; @import 'components/explorer_chart_label/index'; -@import 'explorer_chart'; diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index c7fcddbe4c515..0210663103ac5 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -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 { )} -
+
{isLoading && } {!isLoading &&
}
diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 1b746647ef38d..9935af37feec6 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -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 {
)} -
+
{isLoading && } - {!isLoading &&
} + {!isLoading && ( +
+ )}
); diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_styles.ts b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_styles.ts new file mode 100644 index 0000000000000..e89877fc03392 --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_chart_styles.ts @@ -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', +}); diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index 9bc1317a9aa50..1f3a2d20eea28 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -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 ( { // 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); diff --git a/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index f39bab106c643..d43603684b043 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -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 diff --git a/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/styles.ts b/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/styles.ts index 7c4315e01688b..0c9ba5619908d 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/styles.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/styles.ts @@ -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, }, }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx index 7ca8bf81ed06e..b197daff4d301 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx @@ -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'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.test.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.test.ts index 1eeaa3521f030..87b2d092ada78 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.test.ts @@ -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', () => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/styles.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.ts similarity index 52% rename from x-pack/platform/plugins/shared/ml/public/application/styles.ts rename to x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.ts index 86f4dbe3bbcd4..d0edd52c68f1e 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/styles.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/get_score_string.ts @@ -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)); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score.tsx index 3c5030706da05..b4baad7b392c8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score.tsx @@ -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, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score_health.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score_health.tsx deleted file mode 100644 index ac179b2ac8337..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/score/score_health.tsx +++ /dev/null @@ -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(({ score }) => { - const scoreCeiling = getScoreString(score); - const color = getSeverityColor(score); - return {scoreCeiling}; -}); - -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'; - } -}; diff --git a/x-pack/test/functional/services/ml/anomaly_charts.ts b/x-pack/test/functional/services/ml/anomaly_charts.ts index 50c639094408e..8f63c8cf3710a 100644 --- a/x-pack/test/functional/services/ml/anomaly_charts.ts +++ b/x-pack/test/functional/services/ml/anomaly_charts.ts @@ -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,