[ML] severity cell with multi-bucket impact support (#46002)

* [ML] severity cell with multi-bucket impact support

* [ML] fix type check

* [ML] fix PR remarks

* [ML] replace css classes with Eui components

* [ML] severity cell with multi-bucket impact support

* [ML] fix type check

* [ML] fix PR remarks

* [ML] replace css classes with Eui components

* [ML] set props for eui flex
This commit is contained in:
Dmitrii 2019-09-19 10:08:21 +02:00 committed by GitHub
parent 1786364104
commit 999e2188af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 15 deletions

View file

@ -4,15 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
// Thresholds for indicating the impact of multi-bucket features in an anomaly.
// As a rule-of-thumb, a threshold value T corresponds to the multi-bucket probability
// being 1000^(T/5) times smaller than the single bucket probability.
// So, for example, for HIGH it is 63 times smaller.
/**
* Thresholds for indicating the impact of multi-bucket features in an anomaly.
* As a rule-of-thumb, a threshold value T corresponds to the multi-bucket probability
* being 1000^(T/5) times smaller than the single bucket probability.
* So, for example, for HIGH it is 63 times smaller.
*/
export const MULTI_BUCKET_IMPACT = {
HIGH: 3,
MEDIUM: 2,
LOW: 1,
NONE: -5
NONE: -5,
};

View file

@ -9,7 +9,6 @@
import {
EuiButtonIcon,
EuiHealth,
EuiLink,
} from '@elastic/eui';
@ -31,12 +30,13 @@ import { InfluencersCell } from './influencers_cell';
import { LinksMenu } from './links_menu';
import { checkPermission } from '../../privilege/check_privilege';
import { mlFieldFormatService } from '../../services/field_format_service';
import { getSeverityColor, isRuleSupported } from '../../../common/util/anomaly_utils';
import { isRuleSupported } from '../../../common/util/anomaly_utils';
import { formatValue } from '../../formatters/format_value';
import {
INFLUENCERS_LIMIT,
ANOMALIES_TABLE_TABS
} from './anomalies_table_constants';
import { SeverityCell } from './severity_cell';
function renderTime(date, aggregationInterval) {
if (aggregationInterval === 'hour') {
@ -101,11 +101,7 @@ export function getColumns(
name: i18n.translate('xpack.ml.anomaliesTable.severityColumnName', {
defaultMessage: 'severity',
}),
render: (score) => (
<EuiHealth color={getSeverityColor(score)} compressed="true">
{score >= 1 ? Math.floor(score) : '< 1'}
</EuiHealth>
),
render: (score, item) => <SeverityCell score={score} multiBucketImpact={item.source.multi_bucket_impact} />,
sortable: true
},
{

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { SeverityCell } from './severity_cell';

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { cleanup, render } from 'react-testing-library';
import { SeverityCell } from './severity_cell';
describe('SeverityCell', () => {
afterEach(cleanup);
test('should render a single-bucket marker with rounded severity score', () => {
const props = {
score: 75.2,
multiBucketImpact: -2,
};
const { container } = render(<SeverityCell {...props} />);
expect(container.textContent).toBe('75');
const svgEl = container.getElementsByTagName('svg').item(0);
expect(svgEl && svgEl.style.fill).toBe('#fe5050');
});
test('should render a multi-bucket marker with low severity score', () => {
const props = {
score: 0.8,
multiBucketImpact: 4,
};
const { container } = render(<SeverityCell {...props} />);
expect(container.textContent).toBe('< 1');
const svgEl = container.getElementsByTagName('svg').item(0);
expect(svgEl && svgEl.getAttribute('fill')).toBe('#d2e9f7');
});
});

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, memo } from 'react';
import { EuiHealth, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact';
import { getSeverityColor } from '../../../../common/util/anomaly_utils';
interface SeverityCellProps {
/**
* Severity score.
*/
score: number;
/**
* Multi-bucket impact score from 5 to 5.
* Anomalies with a multi-bucket impact value of greater than or equal
* to 2 are indicated with a plus shaped symbol in the cell.
*/
multiBucketImpact: number;
}
/**
* Renders anomaly severity score with single or multi-bucket impact marker.
*/
export const SeverityCell: FC<SeverityCellProps> = memo(({ score, multiBucketImpact }) => {
const severity = score >= 1 ? Math.floor(score) : '< 1';
const color = getSeverityColor(score);
const isMultiBucket = multiBucketImpact >= MULTI_BUCKET_IMPACT.MEDIUM;
return isMultiBucket ? (
<EuiFlexGroup gutterSize="xs" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<svg width="16" height="16" viewBox="-2 -2 20 20" fill={color}>
<path
d="M-6.708203932499369,-2.23606797749979H-2.23606797749979V-6.708203932499369H2.23606797749979V-2.23606797749979H6.708203932499369V2.23606797749979H2.23606797749979V6.708203932499369H-2.23606797749979V2.23606797749979H-6.708203932499369Z"
transform="translate(8,8)"
></path>
</svg>
</EuiFlexItem>
<EuiFlexItem grow={false}>{severity}</EuiFlexItem>
</EuiFlexGroup>
) : (
<EuiHealth color={color}>{severity}</EuiHealth>
);
});

View file

@ -32,7 +32,7 @@ export interface State {
jobId: DataFrameAnalyticsId;
jobIdExists: boolean;
jobIdEmpty: boolean;
jobIdInvalidMaxLength?: boolean;
jobIdInvalidMaxLength: boolean;
jobIdValid: boolean;
sourceIndex: EsIndexName;
sourceIndexNameEmpty: boolean;
@ -66,6 +66,7 @@ export const getInitialState = (): State => ({
jobId: '',
jobIdExists: false,
jobIdEmpty: true,
jobIdInvalidMaxLength: false,
jobIdValid: false,
sourceIndex: '',
sourceIndexNameEmpty: true,