Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule
</EuiFlyoutHeader>
<EuiFlyoutBody>
<DetectorDescriptionList
anomaly={
Object {
"detectorIndex": 0,
"jobId": "farequote_no_by",
"source": Object {
"function": "mean",
},
}
}
detector={
Object {
"detector_description": "mean(responsetime)",
Expand Down Expand Up @@ -252,6 +261,15 @@ exports[`RuleEditorFlyout renders the flyout after setting the rule to edit 1`]
</EuiFlyoutHeader>
<EuiFlyoutBody>
<DetectorDescriptionList
anomaly={
Object {
"detectorIndex": 1,
"jobId": "farequote_no_by",
"source": Object {
"function": "max",
},
}
}
detector={
Object {
"custom_rules": Array [
Expand Down Expand Up @@ -487,6 +505,15 @@ exports[`RuleEditorFlyout renders the flyout for creating a rule with conditions
</EuiFlyoutHeader>
<EuiFlyoutBody>
<DetectorDescriptionList
anomaly={
Object {
"detectorIndex": 0,
"jobId": "farequote_no_by",
"source": Object {
"function": "mean",
},
}
}
detector={
Object {
"detector_description": "mean(responsetime)",
Expand Down Expand Up @@ -700,6 +727,7 @@ exports[`RuleEditorFlyout renders the select action component for a detector wit
</EuiFlyoutHeader>
<EuiFlyoutBody>
<SelectRuleAction
addItemToFilterList={[Function]}
anomaly={
Object {
"detectorIndex": 1,
Expand All @@ -710,7 +738,6 @@ exports[`RuleEditorFlyout renders the select action component for a detector wit
}
}
deleteRuleAtIndex={[Function]}
detectorIndex={1}
job={
Object {
"analysis_config": Object {
Expand Down Expand Up @@ -749,6 +776,7 @@ exports[`RuleEditorFlyout renders the select action component for a detector wit
}
}
setEditRuleIndex={[Function]}
updateRuleAtIndex={[Function]}
/>
</EuiFlyoutBody>
<EuiFlyoutFooter>
Expand Down
134 changes: 134 additions & 0 deletions x-pack/plugins/ml/public/components/rule_editor/__tests__/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 expect from 'expect.js';
import {
isValidRule,
buildRuleDescription,
getAppliesToValueFromAnomaly,
} from '../utils';
import {
ACTION,
APPLIES_TO,
OPERATOR,
FILTER_TYPE,
} from '../../../../common/constants/detector_rule';

describe('ML - rule editor utils', () => {

const ruleWithCondition = {
actions: [ACTION.SKIP_RESULT],
conditions: [
{
applies_to: APPLIES_TO.ACTUAL,
operator: OPERATOR.GREATER_THAN,
value: 10
}
]
};

const ruleWithScope = {
actions: [ACTION.SKIP_RESULT],
scope: {
instance: {
filter_id: 'test_aws_instances',
filter_type: FILTER_TYPE.INCLUDE,
enabled: true
}
}
};

const ruleWithConditionAndScope = {
actions: [ACTION.SKIP_RESULT],
conditions: [
{
applies_to: APPLIES_TO.TYPICAL,
operator: OPERATOR.LESS_THAN,
value: 100
}
],
scope: {
instance: {
filter_id: 'test_aws_instances',
filter_type: FILTER_TYPE.EXCLUDE,
enabled: true
}
}
};

describe('isValidRule', () => {

it('returns true for a rule with an action and a condition', () => {
expect(isValidRule(ruleWithCondition)).to.be(true);
});

it('returns true for a rule with an action and scope', () => {
expect(isValidRule(ruleWithScope)).to.be(true);
});

it('returns true for a rule with an action, scope and condition', () => {
expect(isValidRule(ruleWithConditionAndScope)).to.be(true);
});

it('returns false for a rule with no action', () => {
const ruleWithNoAction = {
actions: [],
conditions: [
{
applies_to: APPLIES_TO.TYPICAL,
operator: OPERATOR.LESS_THAN,
value: 100
}
],
};

expect(isValidRule(ruleWithNoAction)).to.be(false);
});

it('returns false for a rule with no scope or conditions', () => {
const ruleWithNoScopeOrCondition = {
actions: [ACTION.SKIP_RESULT],
};

expect(isValidRule(ruleWithNoScopeOrCondition)).to.be(false);
});

});

describe('buildRuleDescription', () => {

it('returns expected rule descriptions', () => {
expect(buildRuleDescription(ruleWithCondition)).to.be(
'skip result when actual is greater than 10');
expect(buildRuleDescription(ruleWithScope)).to.be(
'skip result when instance is in test_aws_instances');
expect(buildRuleDescription(ruleWithConditionAndScope)).to.be(
'skip result when typical is less than 100 AND instance is not in test_aws_instances');
});
});

describe('getAppliesToValueFromAnomaly', () => {

const anomaly = {
actual: [210],
typical: [1.23],
};

it('returns expected actual value from an anomaly', () => {
expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.ACTUAL)).to.be(210);
});

it('returns expected typical value from an anomaly', () => {
expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.TYPICAL)).to.be(1.23);
});

it('returns expected diff from typical value from an anomaly', () => {
expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.DIFF_FROM_TYPICAL)).to.be(208.77);
});
});

});
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`DetectorDescriptionList render for farequote detector 1`] = `
exports[`DetectorDescriptionList render for detector with anomaly values 1`] = `
<EuiDescriptionList
align="left"
className="rule-detector-description-list"
compressed={false}
listItems={
Array [
Object {
"description": "farequote",
"title": "job ID",
"description": "responsetimes",
"title": "Job ID",
},
Object {
"description": "mean response time",
"title": "detector",
"title": "Detector",
},
Object {
"description": "actual 50, typical 1.23",
"title": "Selected anomaly",
},
]
}
textStyle="normal"
type="column"
/>
`;

exports[`DetectorDescriptionList render for population detector with no anomaly values 1`] = `
<EuiDescriptionList
align="left"
className="rule-detector-description-list"
compressed={false}
listItems={
Array [
Object {
"description": "population",
"title": "Job ID",
},
Object {
"description": "count by status over clientip",
"title": "Detector",
},
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,41 @@ import {
EuiDescriptionList,
} from '@elastic/eui';

import { formatValue } from '../../../../formatters/format_value';

import './styles/main.less';

export function DetectorDescriptionList({
job,
detector }) {
detector,
anomaly, }) {

const listItems = [
{
title: 'job ID',
title: 'Job ID',
description: job.job_id,
},
{
title: 'detector',
title: 'Detector',
description: detector.detector_description,
}
];

if (anomaly.actual !== undefined) {
// Format based on magnitude of value at this stage, rather than using the
// Kibana field formatter (if set) which would add complexity converting
// the entered value to / from e.g. bytes.
const actual = formatValue(anomaly.actual, anomaly.source.function);
const typical = formatValue(anomaly.typical, anomaly.source.function);

listItems.push(
{
title: 'Selected anomaly',
description: `actual ${actual}, typical ${typical}`,
}
);
}

return (
<EuiDescriptionList
className="rule-detector-description-list"
Expand All @@ -45,5 +63,6 @@ export function DetectorDescriptionList({
DetectorDescriptionList.propTypes = {
job: PropTypes.object.isRequired,
detector: PropTypes.object.isRequired,
anomaly: PropTypes.object.isRequired,
};

Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,52 @@ import { DetectorDescriptionList } from './detector_description_list';

describe('DetectorDescriptionList', () => {

test('render for farequote detector', () => {
test('render for detector with anomaly values', () => {

const props = {
job: {
job_id: 'farequote'
job_id: 'responsetimes'
},
detector: {
detector_description: 'mean response time'
}
},
anomaly: {
actual: [50],
typical: [1.23],
source: { function: 'mean' },
},
};

const component = shallow(
<DetectorDescriptionList {...props} />
);

expect(component).toMatchSnapshot();

});

test('render for population detector with no anomaly values', () => {

const props = {
job: {
job_id: 'population'
},
detector: {
detector_description: 'count by status over clientip'
},
anomaly: {
source: { function: 'count' },
causes: [
{
actual: [50],
typical: [1.01]
},
{
actual: [60],
typical: [1.2]
},
],
},
};

const component = shallow(
Expand Down
Loading