diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data_rare.js b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data_rare.js
new file mode 100644
index 0000000000000..3cc8b41ea7ac3
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data_rare.js
@@ -0,0 +1,40 @@
+/*
+ * 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 const chartData = [
+ {
+ date: 1487899800000,
+ entity: '200',
+ value: 1741.5652200000002
+ },
+ {
+ date: 1487899800000,
+ entity: '404',
+ value: 494.30564000000004
+ },
+ {
+ date: 1487899800000,
+ entity: '304',
+ value: 160.93672
+ },
+ {
+ date: 1487899800000,
+ entity: '301',
+ value: 57.4774
+ },
+ {
+ date: 1487837700000,
+ value: 42,
+ entity: '303',
+ anomalyScore: 84.08759,
+ actual: [
+ 1
+ ],
+ typical: [
+ 0.00028318796131582025
+ ]
+ }
+];
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_rare.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_rare.json
index b82583a730323..07c13d7cd8b7f 100644
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_rare.json
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_rare.json
@@ -1,13 +1,13 @@
{
- "jobId": "ffb-rare-url-0921",
+ "jobId": "ffb-rare-by-response-code-0942",
"detectorIndex": 0,
"metricFunction": "count",
"timeField": "@timestamp",
"interval": "15m",
"datafeedConfig": {
- "datafeed_id": "datafeed-ffb-rare-url-0921",
- "job_id": "ffb-rare-url-0921",
- "query_delay": "115433ms",
+ "datafeed_id": "datafeed-ffb-rare-by-response-code-0942",
+ "job_id": "ffb-rare-by-response-code-0942",
+ "query_delay": "66615ms",
"indices": [
"filebeat-6.0.0-2017-nginx-elasticco-anon"
],
@@ -25,25 +25,32 @@
},
"functionDescription": "rare",
"bucketSpanSeconds": 900,
- "detectorLabel": "rare by \"nginx.access.url\"",
+ "detectorLabel": "rare by \"nginx.access.response_code\"",
"entityFields": [
{
- "fieldName": "nginx.access.url",
- "fieldValue": "/?node=4.1.1,5,7",
+ "fieldName": "nginx.access.response_code",
+ "fieldValue": "303",
"fieldType": "by"
}
],
"infoTooltip": {
- "jobId": "ffb-rare-url-0921",
+ "jobId": "ffb-rare-by-response-code-0942",
"aggregationInterval": "15m",
"chartFunction": "count",
"entityFields": [
{
- "fieldName": "nginx.access.url",
- "fieldValue": "/?node=4.1.1,5,7"
+ "fieldName": "nginx.access.response_code",
+ "fieldValue": "303"
}
]
},
- "loading": true,
- "chartData": null
+ "loading": false,
+ "plotEarliest": 1487774250000,
+ "plotLatest": 1487900250000,
+ "selectedEarliest": 1487836800000,
+ "selectedLatest": 1487837699999,
+ "chartLimits": {
+ "max": 9294.095580000001,
+ "min": 5.74774
+ }
}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js
index 151bd60ad4c6e..26a1d7758f46f 100644
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js
@@ -39,6 +39,13 @@ import { CHART_TYPE } from '../explorer_constants';
const CONTENT_WRAPPER_HEIGHT = 215;
+// If a rare/event-distribution chart has a cardinality of 10 or less,
+// then the chart will display the y axis labels for each lane of events.
+// If cardinality is higher, then the axis will just be hidden.
+// Cardinality in this case refers to the available for display,
+// not the cardinality of the full source data set.
+const Y_AXIS_LABEL_THRESHOLD = 10;
+
export class ExplorerChartDistribution extends React.Component {
static propTypes = {
seriesConfig: PropTypes.object,
@@ -189,8 +196,15 @@ export class ExplorerChartDistribution extends React.Component {
.remove();
d3.select('.temp-axis-label').remove();
- // Set the size of the left margin according to the width of the largest y axis tick label.
- if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) {
+ // Set the size of the left margin according to the width of the largest y axis tick label
+ // if the chart is either a population chart or a rare chart below the cardinality threshold.
+ if (
+ chartType === CHART_TYPE.POPULATION_DISTRIBUTION
+ || (
+ chartType === CHART_TYPE.EVENT_DISTRIBUTION
+ && scaleCategories.length <= Y_AXIS_LABEL_THRESHOLD
+ )
+ ) {
margin.left = (Math.max(maxYAxisLabelWidth, 40));
}
vizWidth = svgWidth - margin.left - margin.right;
@@ -281,6 +295,13 @@ export class ExplorerChartDistribution extends React.Component {
.attr('class', 'y axis')
.call(yAxis);
+ // emphasize the y axis label this rare chart is actually about
+ if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) {
+ axes.select('.y').selectAll('text').each(function (d) {
+ d3.select(this).classed('ml-explorer-chart-axis-emphasis', (d === highlight));
+ });
+ }
+
if (tooManyBuckets === false) {
removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth);
}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.test.js
new file mode 100644
index 0000000000000..9e2a3fc85122f
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.test.js
@@ -0,0 +1,162 @@
+/*
+ * 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 { chartData as mockChartData } from './__mocks__/mock_chart_data_rare';
+import seriesConfig from './__mocks__/mock_series_config_rare.json';
+
+// Mock TimeBuckets and mlFieldFormatService, they don't play well
+// with the jest based test setup yet.
+jest.mock('ui/time_buckets', () => ({
+ TimeBuckets: function () {
+ this.setBounds = jest.fn();
+ this.setInterval = jest.fn();
+ this.getScaledDateFormat = jest.fn();
+ }
+}));
+jest.mock('../../services/field_format_service', () => ({
+ mlFieldFormatService: {
+ getFieldFormat: jest.fn()
+ }
+}));
+jest.mock('ui/chrome', () => ({
+ getBasePath: (path) => path,
+ getUiSettingsClient: () => ({
+ get: () => null
+ }),
+}));
+
+import { mount } from 'enzyme';
+import React from 'react';
+
+import { ExplorerChartDistribution } from './explorer_chart_distribution';
+import { chartLimits } from '../../util/chart_utils';
+
+describe('ExplorerChart', () => {
+ const mlSelectSeverityServiceMock = {
+ state: {
+ get: () => ({
+ val: ''
+ })
+ }
+ };
+
+ const mockedGetBBox = { x: 0, y: -11.5, width: 12.1875, height: 14.5 };
+ const originalGetBBox = SVGElement.prototype.getBBox;
+ beforeEach(() => SVGElement.prototype.getBBox = () => mockedGetBBox);
+ afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox));
+
+ test('Initialize', () => {
+ const wrapper = mount(