[ML] Prevents conditions on rules for rare, metric and lat_long (#21198)

This commit is contained in:
Pete Harverson 2018-07-25 12:49:03 +01:00 committed by GitHub
parent 967cb4e7fb
commit 755409d31d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 157 additions and 16 deletions

View file

@ -31,3 +31,11 @@ export const OPERATOR = {
GREATER_THAN: 'gt',
GREATER_THAN_OR_EQUAL: 'gte',
};
// List of detector functions which don't support rules with numeric conditions.
export const CONDITIONS_NOT_SUPPORTED_FUNCTIONS = [
'freq_rare',
'lat_long',
'metric',
'rare',
];

View file

@ -16,6 +16,7 @@ import {
getEntityFieldValue,
showActualForFunction,
showTypicalForFunction,
isRuleSupported,
aggregationTypeTransform
} from '../anomaly_utils';
@ -81,6 +82,103 @@ describe('ML - anomaly utils', () => {
'field_name': 'responsetime'
};
const metricNoEntityRecord = {
'job_id': 'farequote_metric',
'result_type': 'record',
'probability': 0.030133495093182184,
'record_score': 0.024881740359975164,
'initial_record_score': 0.024881740359975164,
'bucket_span': 900,
'detector_index': 0,
'is_interim': false,
'timestamp': 1486845000000,
'function': 'metric',
'function_description': 'mean',
'typical': [
545.7764658569108
],
'actual': [
758.8220213274412
],
'field_name': 'responsetime',
'influencers': [
{
'influencer_field_name': 'airline',
'influencer_field_values': [
'NKS'
]
}
],
'airline': [
'NKS'
]
};
const rareEntityRecord = {
'job_id': 'gallery',
'result_type': 'record',
'probability': 0.02277014211908481,
'record_score': 4.545378107075983,
'initial_record_score': 4.545378107075983,
'bucket_span': 3600,
'detector_index': 0,
'is_interim': false,
'timestamp': 1495879200000,
'by_field_name': 'status',
'function': 'rare',
'function_description': 'rare',
'over_field_name': 'clientip',
'over_field_value': '173.252.74.112',
'causes': [
{
'probability': 0.02277014211908481,
'by_field_name': 'status',
'by_field_value': '206',
'function': 'rare',
'function_description': 'rare',
'typical': [
0.00014832458182211878
],
'actual': [
1
],
'over_field_name': 'clientip',
'over_field_value': '173.252.74.112'
}
],
'influencers': [
{
'influencer_field_name': 'uri',
'influencer_field_values': [
'/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg',
'/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png'
]
},
{
'influencer_field_name': 'status',
'influencer_field_values': [
'206'
]
},
{
'influencer_field_name': 'clientip',
'influencer_field_values': [
'173.252.74.112'
]
}
],
'clientip': [
'173.252.74.112'
],
'uri': [
'/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg',
'/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png'
],
'status': [
'206'
]
};
describe('getSeverity', () => {
it('returns warning for 0 <= score < 25', () => {
@ -282,6 +380,21 @@ describe('ML - anomaly utils', () => {
});
describe('isRuleSupported', () => {
it('returns true for anomalies supporting rules', () => {
expect(isRuleSupported(partitionEntityRecord)).to.be(true);
expect(isRuleSupported(byEntityRecord)).to.be(true);
expect(isRuleSupported(overEntityRecord)).to.be(true);
expect(isRuleSupported(rareEntityRecord)).to.be(true);
expect(isRuleSupported(noEntityRecord)).to.be(true);
});
it('returns false for anomaly not supporting rules', () => {
expect(isRuleSupported(metricNoEntityRecord)).to.be(false);
});
});
describe('aggregationTypeTransform', () => {
it('returns correct ES aggregation type for ML function description', () => {
expect(aggregationTypeTransform.toES('count')).to.be('count');

View file

@ -12,6 +12,7 @@
*/
import _ from 'lodash';
import { CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from '../constants/detector_rule';
// List of function descriptions for which actual values from record level results should be displayed.
const DISPLAY_ACTUAL_FUNCTIONS = ['count', 'distinct_count', 'lat_long', 'mean', 'max', 'min', 'sum',
@ -152,6 +153,14 @@ export function showTypicalForFunction(functionDescription) {
return _.indexOf(DISPLAY_TYPICAL_FUNCTIONS, functionDescription) > -1;
}
// Returns whether a rule can be configured against the specified anomaly.
export function isRuleSupported(record) {
// A rule can be configured with a numeric condition if the function supports it,
// and/or with scope if there is a partitioning fields.
return (CONDITIONS_NOT_SUPPORTED_FUNCTIONS.indexOf(record.function) === -1) ||
(getEntityFieldName(record) !== undefined);
}
// Two functions for converting aggregation type names.
// ML and ES use different names for the same function.
// Possible values for ML aggregation type are (defined in lib/model/CAnomalyDetector.cc):

View file

@ -37,7 +37,7 @@ import { checkPermission } from 'plugins/ml/privilege/check_privilege';
import { mlAnomaliesTableService } from './anomalies_table_service';
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
import { getSeverityColor } from 'plugins/ml/../common/util/anomaly_utils';
import { getSeverityColor, isRuleSupported } from 'plugins/ml/../common/util/anomaly_utils';
import { formatValue } from 'plugins/ml/formatters/format_value';
import { RuleEditorFlyout } from 'plugins/ml/components/rule_editor';
@ -56,8 +56,8 @@ function renderTime(date, aggregationInterval) {
}
function showLinksMenuForItem(item) {
const canUpdateJob = checkPermission('canUpdateJob');
return (canUpdateJob ||
const canConfigureRules = (isRuleSupported(item) && checkPermission('canUpdateJob'));
return (canConfigureRules ||
item.isTimeSeriesViewDetector ||
item.entityName === 'mlcategory' ||
item.customUrls !== undefined);

View file

@ -23,6 +23,7 @@ import { toastNotifications } from 'ui/notify';
import { ES_FIELD_TYPES } from 'plugins/ml/../common/constants/field_types';
import { checkPermission } from 'plugins/ml/privilege/check_privilege';
import { isRuleSupported } from 'plugins/ml/../common/util/anomaly_utils';
import { parseInterval } from 'plugins/ml/../common/util/parse_interval';
import { getFieldTypeFromMapping } from 'plugins/ml/services/mapping_service';
import { ml } from 'plugins/ml/services/ml_api_service';
@ -336,7 +337,7 @@ export class LinksMenu extends Component {
render() {
const { anomaly, showViewSeriesLink } = this.props;
const canUpdateJob = checkPermission('canUpdateJob');
const canConfigureRules = (isRuleSupported(anomaly.source) && checkPermission('canUpdateJob'));
const button = (
<EuiButtonIcon
@ -387,7 +388,7 @@ export class LinksMenu extends Component {
);
}
if (canUpdateJob) {
if (canConfigureRules) {
items.push(
<EuiContextMenuItem
key="create_rule"

View file

@ -44,7 +44,7 @@ import {
deleteJobRule
} from './utils';
import { ACTION } from '../../../common/constants/detector_rule';
import { ACTION, CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from '../../../common/constants/detector_rule';
import { getPartitioningFieldNames } from 'plugins/ml/../common/util/job_utils';
import { mlJobService } from 'plugins/ml/services/job_service';
import { ml } from 'plugins/ml/services/ml_api_service';
@ -366,8 +366,6 @@ export class RuleEditorFlyout extends Component {
let flyout;
const hasPartitioningFields = (this.partitioningFieldNames && this.partitioningFieldNames.length > 0);
if (ruleIndex === -1) {
flyout = (
<EuiFlyout
@ -409,9 +407,12 @@ export class RuleEditorFlyout extends Component {
</EuiFlyout>
);
} else {
const hasPartitioningFields = (this.partitioningFieldNames && this.partitioningFieldNames.length > 0);
const conditionSupported = (CONDITIONS_NOT_SUPPORTED_FUNCTIONS.indexOf(anomaly.source.function) === -1);
const conditionsText = 'Add numeric conditions to take action according ' +
'to the actual or typical values of the anomaly. Multiple conditions are ' +
'combined using AND.';
flyout = (
<EuiFlyout
className="ml-rule-editor-flyout"
@ -452,14 +453,23 @@ export class RuleEditorFlyout extends Component {
<h2>Conditions</h2>
</EuiTitle>
<EuiSpacer size="s" />
<EuiCheckbox
id="enable_conditions_checkbox"
className="scope-enable-checkbox"
label={conditionsText}
checked={isConditionsEnabled}
onChange={this.onConditionsEnabledChange}
disabled={!hasPartitioningFields}
/>
{(conditionSupported === true) ?
(
<EuiCheckbox
id="enable_conditions_checkbox"
className="scope-enable-checkbox"
label={conditionsText}
checked={isConditionsEnabled}
onChange={this.onConditionsEnabledChange}
disabled={!conditionSupported || !hasPartitioningFields}
/>
) : (
<EuiCallOut
title={`Conditions are not supported for detectors using the ${anomaly.source.function} function`}
iconType="iInCircle"
/>
)
}
<EuiSpacer size="s" />
<ConditionsSection
isEnabled={isConditionsEnabled}