Skip to content

Commit

Permalink
[ML] Adds editor for configuring detector rules (elastic#20989) (elas…
Browse files Browse the repository at this point in the history
…tic#21067)

* [ML] Adds editor for configuring detector rules

* [ML] Edits to Rule Editor flyout following review
  • Loading branch information
peteharverson authored Jul 22, 2018
1 parent d5a8277 commit 5561cfe
Show file tree
Hide file tree
Showing 19 changed files with 2,015 additions and 27 deletions.
33 changes: 33 additions & 0 deletions x-pack/plugins/ml/common/constants/detector_rule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.
*/

/*
* Contains values for ML job detector rules.
*/


export const ACTION = {
SKIP_MODEL_UPDATE: 'skip_model_update',
SKIP_RESULT: 'skip_result',
};

export const FILTER_TYPE = {
EXCLUDE: 'exclude',
INCLUDE: 'include',
};

export const APPLIES_TO = {
ACTUAL: 'actual',
DIFF_FROM_TYPICAL: 'diff_from_typical',
TYPICAL: 'typical',
};

export const OPERATOR = {
LESS_THAN: 'lt',
LESS_THAN_OR_EQUAL: 'lte',
GREATER_THAN: 'gt',
GREATER_THAN_OR_EQUAL: 'gte',
};
63 changes: 63 additions & 0 deletions x-pack/plugins/ml/common/util/__tests__/job_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isTimeSeriesViewJob,
isTimeSeriesViewDetector,
isTimeSeriesViewFunction,
getPartitioningFieldNames,
isModelPlotEnabled,
isJobVersionGte,
mlFunctionToESAggregation,
Expand Down Expand Up @@ -201,6 +202,68 @@ describe('ML - job utils', () => {
});
});

describe('getPartitioningFieldNames', () => {
const job = {
analysis_config: {
detectors: [
{
function: 'count',
detector_description: 'count'
},
{
function: 'count',
partition_field_name: 'clientip',
detector_description: 'Count by clientip'
},
{
function: 'freq_rare',
by_field_name: 'uri',
over_field_name: 'clientip',
detector_description: 'Freq rare URI'
},
{
function: 'sum',
field_name: 'bytes',
by_field_name: 'uri',
over_field_name: 'clientip',
partition_field_name: 'method',
detector_description: 'sum bytes'
},
]
}
};

it('returns empty array for a detector with no partitioning fields', () => {
const resp = getPartitioningFieldNames(job, 0);
expect(resp).to.be.an('array');
expect(resp).to.be.empty();
});

it('returns expected array for a detector with a partition field', () => {
const resp = getPartitioningFieldNames(job, 1);
expect(resp).to.be.an('array');
expect(resp).to.have.length(1);
expect(resp).to.contain('clientip');
});

it('returns expected array for a detector with by and over fields', () => {
const resp = getPartitioningFieldNames(job, 2);
expect(resp).to.be.an('array');
expect(resp).to.have.length(2);
expect(resp).to.contain('uri');
expect(resp).to.contain('clientip');
});

it('returns expected array for a detector with partition, by and over fields', () => {
const resp = getPartitioningFieldNames(job, 3);
expect(resp).to.be.an('array');
expect(resp).to.have.length(3);
expect(resp).to.contain('uri');
expect(resp).to.contain('clientip');
expect(resp).to.contain('method');
});
});

describe('isModelPlotEnabled', () => {

it('returns true for a job in which model plot has been enabled', () => {
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/ml/common/util/job_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ export function isTimeSeriesViewFunction(functionName) {
return mlFunctionToESAggregation(functionName) !== null;
}

// Returns the names of the partition, by, and over fields for the detector with the
// specified index from the supplied ML job configuration.
export function getPartitioningFieldNames(job, detectorIndex) {
const fieldNames = [];
const detector = job.analysis_config.detectors[detectorIndex];
if (_.has(detector, 'partition_field_name')) {
fieldNames.push(detector.partition_field_name);
}
if (_.has(detector, 'by_field_name')) {
fieldNames.push(detector.by_field_name);
}
if (_.has(detector, 'over_field_name')) {
fieldNames.push(detector.over_field_name);
}

return fieldNames;
}

// Returns a flag to indicate whether model plot has been enabled for a job.
// If model plot is enabled for a job with a terms filter (comma separated
// list of partition or by field names), performs additional checks that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ 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 { formatValue } from 'plugins/ml/formatters/format_value';
import { RuleEditorFlyout } from 'plugins/ml/components/rule_editor';


const INFLUENCERS_LIMIT = 5; // Maximum number of influencers to display before a 'show more' link is added.
Expand All @@ -53,7 +54,10 @@ function renderTime(date, aggregationInterval) {
}

function showLinksMenuForItem(item) {
return item.isTimeSeriesViewDetector ||
// TODO - add in checking of user privileges to see if they can view / edit rules.
const canViewRules = true;
return canViewRules ||
item.isTimeSeriesViewDetector ||
item.entityName === 'mlcategory' ||
item.customUrls !== undefined;
}
Expand All @@ -65,9 +69,11 @@ function getColumns(
interval,
timefilter,
showViewSeriesLink,
showRuleEditorFlyout,
itemIdToExpandedRowMap,
toggleRow,
filter) {

const columns = [
{
name: '',
Expand Down Expand Up @@ -186,12 +192,11 @@ function getColumns(
sortable: true
});

const showExamples = items.some(item => item.entityName === 'mlcategory');
const showLinks = (showViewSeriesLink === true) || items.some(item => showLinksMenuForItem(item));

if (showLinks === true) {
columns.push({
name: 'links',
name: 'actions',
render: (item) => {
if (showLinksMenuForItem(item) === true) {
return (
Expand All @@ -201,6 +206,7 @@ function getColumns(
isAggregatedData={isAggregatedData}
interval={interval}
timefilter={timefilter}
showRuleEditorFlyout={showRuleEditorFlyout}
/>
);
} else {
Expand All @@ -211,6 +217,7 @@ function getColumns(
});
}

const showExamples = items.some(item => item.entityName === 'mlcategory');
if (showExamples === true) {
columns.push({
name: 'category examples',
Expand Down Expand Up @@ -238,7 +245,8 @@ class AnomaliesTable extends Component {
super(props);

this.state = {
itemIdToExpandedRowMap: {}
itemIdToExpandedRowMap: {},
showRuleEditorFlyout: () => {}
};
}

Expand Down Expand Up @@ -313,6 +321,19 @@ class AnomaliesTable extends Component {
}
};

setShowRuleEditorFlyoutFunction = (func) => {
this.setState({
showRuleEditorFlyout: func
});
}

unsetShowRuleEditorFlyoutFunction = () => {
const showRuleEditorFlyout = () => {};
this.setState({
showRuleEditorFlyout
});
}

render() {
const { timefilter, tableData, filter } = this.props;

Expand All @@ -336,6 +357,7 @@ class AnomaliesTable extends Component {
tableData.interval,
timefilter,
tableData.showViewSeriesLink,
this.state.showRuleEditorFlyout,
this.state.itemIdToExpandedRowMap,
this.toggleRow,
filter);
Expand All @@ -355,20 +377,26 @@ class AnomaliesTable extends Component {
};

return (
<EuiInMemoryTable
className="ml-anomalies-table eui-textBreakWord"
items={tableData.anomalies}
columns={columns}
pagination={{
pageSizeOptions: [10, 25, 100],
initialPageSize: 25
}}
sorting={sorting}
itemId="rowId"
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
compressed={true}
rowProps={getRowProps}
/>
<React.Fragment>
<RuleEditorFlyout
setShowFunction={this.setShowRuleEditorFlyoutFunction}
unsetShowFunction={this.unsetShowRuleEditorFlyoutFunction}
/>
<EuiInMemoryTable
className="ml-anomalies-table eui-textBreakWord"
items={tableData.anomalies}
columns={columns}
pagination={{
pageSizeOptions: [10, 25, 100],
initialPageSize: 25
}}
sorting={sorting}
itemId="rowId"
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
compressed={true}
rowProps={getRowProps}
/>
</React.Fragment>
);
}
}
Expand Down
27 changes: 18 additions & 9 deletions x-pack/plugins/ml/public/components/anomalies_table/links_menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';

import {
EuiButtonEmpty,
EuiButtonIcon,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiPopover
Expand Down Expand Up @@ -337,15 +337,13 @@ export class LinksMenu extends Component {
const { anomaly, showViewSeriesLink } = this.props;

const button = (
<EuiButtonEmpty
<EuiButtonIcon
size="s"
type="text"
iconType="arrowDown"
iconSide="right"
color="text"
onClick={this.onButtonClick}
>
Open link
</EuiButtonEmpty>
iconType="gear"
aria-label="Select action"
/>
);

const items = [];
Expand Down Expand Up @@ -387,6 +385,16 @@ export class LinksMenu extends Component {
);
}

items.push(
<EuiContextMenuItem
key="create_rule"
icon="controlsHorizontal"
onClick={() => { this.closePopover(); this.props.showRuleEditorFlyout(anomaly); }}
>
Configure rules
</EuiContextMenuItem>
);

return (
<EuiPopover
id="singlePanel"
Expand All @@ -409,5 +417,6 @@ LinksMenu.propTypes = {
showViewSeriesLink: PropTypes.bool,
isAggregatedData: PropTypes.bool,
interval: PropTypes.string,
timefilter: PropTypes.object.isRequired
timefilter: PropTypes.object.isRequired,
showRuleEditorFlyout: PropTypes.func
};
Loading

0 comments on commit 5561cfe

Please sign in to comment.