diff --git a/x-pack/plugins/ml/common/constants/detector_rule.js b/x-pack/plugins/ml/common/constants/detector_rule.js index cb8c7a71d59ef..c3cdb250f7669 100644 --- a/x-pack/plugins/ml/common/constants/detector_rule.js +++ b/x-pack/plugins/ml/common/constants/detector_rule.js @@ -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', +]; diff --git a/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js b/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js index 0f47254bb90cc..e583fe13c8a5f 100644 --- a/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js +++ b/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js @@ -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'); diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.js b/x-pack/plugins/ml/common/util/anomaly_utils.js index d367a061440fb..2f5baafec249c 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.js +++ b/x-pack/plugins/ml/common/util/anomaly_utils.js @@ -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): diff --git a/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js index d7b2794fab9cf..97a89244e3250 100644 --- a/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js @@ -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); diff --git a/x-pack/plugins/ml/public/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/components/anomalies_table/links_menu.js index fe7e02e9b8b46..61209dc1b5f51 100644 --- a/x-pack/plugins/ml/public/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/components/anomalies_table/links_menu.js @@ -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 = ( 0); - if (ruleIndex === -1) { flyout = ( ); } 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 = ( Conditions - + {(conditionSupported === true) ? + ( + + ) : ( + + ) + }