Skip to content

Commit

Permalink
[ML] Improve Explorer Chart labels. (#23494)
Browse files Browse the repository at this point in the history
Improves the display of the Explorer Chart labels to fix the following issues:
- Long chart labels could be cut off, so it's not possible to tell what entity fields a chart is referring to. A workaround is to hover the info icon tooltip but that's really slow and cumbersome if you have to do it for every chart.
- The list of entity fields and its values is an unformatted text blob which makes it hard to read and tell which values refer to which field.

Changes:
- If any of the chart labels is longer than 60 chars, the entity fields will wrap to a new line (for all charts to a achieve a consistent look).
- Entity fields use EuiBadge and some custom formatting to make it easier to see field/value pairs.
- If the detector description is too long, it still uses ellipsis for text-overflow:
- If the entity badges are too long, they will be just cut off to the right. There's no simple CSS fix for that, we cannot use ellipsis and we don't want to wrap those badges again because then multiple charts could have different heights. I experimented with gradients but that turned out to be somewhat unreliable. I still consider this a good enough improvement compare to the previous version and would like to leave a tweak for that to a follow up PR.
- If there are mixed detectors with and without entity fields and the existing one wrap, multiple charts are aligned considered the height of the entity fields on display:
- Additionally, this changes the link to the single series viewer from custom code using a Font Awesome icon to use EuiButtonEmpty with the same EUI based icon and a tooltip.
  • Loading branch information
walterra authored Sep 27, 2018
1 parent 186cea2 commit 0ff498d
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 120 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/ml/public/explorer/explorer.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ <h4 class="euiTitle euiTitle--small">No {{swimlaneViewByFieldName}} influencers
</div>
</div>

<div class="euiSpacer euiSpacer--m"></div>

<div class="euiText explorer-charts">
<ml-explorer-charts-container />
</div>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ExplorerChartLabelBadge Render the chart label in one line. 1`] = `
<React.Fragment>
<span
className="ml-explorer-chart-label"
>
<span
className="ml-explorer-chart-label-detector"
>
high_sum(nginx.access.body_sent.bytes) over nginx.access.remote_ip (population-03)
<React.Fragment>
 – 
</React.Fragment>
</span>
<React.Fragment>
<React.Fragment
key="nginx.access.remote_ip 72.57.0.53"
>
<ExplorerChartLabelBadge
entity={
Object {
"$$hashKey": "object:813",
"fieldName": "nginx.access.remote_ip",
"fieldValue": "72.57.0.53",
}
}
/>
 
</React.Fragment>
<span
className="ml-explorer-chart-info-icon"
>
<EuiIconTip
aria-label="Info"
content={
<ExplorerChartTooltip
aggregationInterval="1h"
chartFunction="sum nginx.access.body_sent.bytes"
entityFields={
Array [
Object {
"fieldName": "nginx.access.remote_ip",
"fieldValue": "72.57.0.53",
},
]
}
jobId="population-03"
/>
}
position="top"
size="s"
type="questionInCircle"
/>
</span>
</React.Fragment>
</span>
</React.Fragment>
`;

exports[`ExplorerChartLabelBadge Render the chart label in two lines. 1`] = `
<React.Fragment>
<span
className="ml-explorer-chart-label"
>
<span
className="ml-explorer-chart-label-detector"
>
high_sum(nginx.access.body_sent.bytes) over nginx.access.remote_ip (population-03)
<React.Fragment>
 
</React.Fragment>
</span>
<span
className="ml-explorer-chart-info-icon"
>
<EuiIconTip
aria-label="Info"
content={
<ExplorerChartTooltip
aggregationInterval="1h"
chartFunction="sum nginx.access.body_sent.bytes"
entityFields={
Array [
Object {
"fieldName": "nginx.access.remote_ip",
"fieldValue": "72.57.0.53",
},
]
}
jobId="population-03"
/>
}
position="top"
size="s"
type="questionInCircle"
/>
</span>
</span>
<div
className="ml-explorer-chart-label-fields"
>
<React.Fragment
key="nginx.access.remote_ip 72.57.0.53"
>
<ExplorerChartLabelBadge
entity={
Object {
"$$hashKey": "object:813",
"fieldName": "nginx.access.remote_ip",
"fieldValue": "72.57.0.53",
}
}
/>
 
</React.Fragment>
</div>
</React.Fragment>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ExplorerChartLabelBadge Render entity label badge. 1`] = `
<span
className="ml-explorer-chart-label-badge"
>
<EuiBadge
color="hollow"
iconSide="left"
>
nginx.access.remote_ip
<strong>
72.57.0.53
</strong>
</EuiBadge>
</span>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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 './styles/explorer_chart_label.less';

import PropTypes from 'prop-types';
import React from 'react';

import {
EuiIconTip
} from '@elastic/eui';

import { ExplorerChartLabelBadge } from './explorer_chart_label_badge';
import { ExplorerChartTooltip } from '../../explorer_chart_tooltip';

export function ExplorerChartLabel({ detectorLabel, entityFields, infoTooltip, wrapLabel = false }) {
// Depending on whether we wrap the entityField badges to a new line, we render this differently:
//
// 1. All in one line:
// <detectorLabel> - <entityBadge1> <entityBadge2> ... <infoIcon>
//
// 2. Multiple lines:
// <detectorLabel> <infoIcon>
// <entityBadge1> <entityBadge2> ...

// Using &nbsp;s here to make sure those spaces get rendered.
const labelSeparator = (
wrapLabel === true ||
(entityFields.length === 0 || detectorLabel.length === 0)
) ? (<React.Fragment>&nbsp;</React.Fragment>) : (<React.Fragment>&nbsp;&ndash;&nbsp;</React.Fragment>);

const entityFieldBadges = entityFields.map((entity) => {
return (
<React.Fragment key={`${entity.fieldName} ${entity.fieldValue}`}>
<ExplorerChartLabelBadge entity={entity} />&nbsp;
</React.Fragment>
);
});

const infoIcon = (
<span className="ml-explorer-chart-info-icon">
<EuiIconTip
content={<ExplorerChartTooltip {...infoTooltip} />}
position="top"
size="s"
/>
</span>
);

return (
<React.Fragment>
<span className="ml-explorer-chart-label">
<span className="ml-explorer-chart-label-detector">
{detectorLabel}{labelSeparator}
</span>
{wrapLabel && infoIcon}
{!wrapLabel && (
<React.Fragment>{entityFieldBadges} {infoIcon}</React.Fragment>
)}
</span>
{wrapLabel && (
<div className="ml-explorer-chart-label-fields">{entityFieldBadges}</div>
)}
</React.Fragment>
);
}
ExplorerChartLabel.propTypes = {
detectorLabel: PropTypes.string.isRequired,
entityFields: PropTypes.arrayOf(ExplorerChartLabelBadge.propTypes.entity),
infoTooltip: PropTypes.object.isRequired,
wrapLabel: PropTypes.bool
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 seriesConfig from '../../__mocks__/mock_series_config_filebeat.json';

import { shallow } from 'enzyme';
import React from 'react';

import { ExplorerChartLabel } from './explorer_chart_label';


describe('ExplorerChartLabelBadge', () => {

test('Render the chart label in one line.', () => {

const wrapper = shallow(
<ExplorerChartLabel
detectorLabel={seriesConfig.detectorLabel}
entityFields={seriesConfig.entityFields}
infoTooltip={seriesConfig.infoTooltip}
wrapLabel={false}
/>
);
expect(wrapper).toMatchSnapshot();

});

test('Render the chart label in two lines.', () => {

const wrapper = shallow(
<ExplorerChartLabel
detectorLabel={seriesConfig.detectorLabel}
entityFields={seriesConfig.entityFields}
infoTooltip={seriesConfig.infoTooltip}
wrapLabel={true}
/>
);
expect(wrapper).toMatchSnapshot();

});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 './styles/explorer_chart_label_badge.less';

import PropTypes from 'prop-types';
import React from 'react';

import {
EuiBadge
} from '@elastic/eui';

export function ExplorerChartLabelBadge({ entity }) {
return (
<span className="ml-explorer-chart-label-badge">
<EuiBadge color="hollow">
{entity.fieldName} <strong>{entity.fieldValue}</strong>
</EuiBadge>
</span>
);
}
ExplorerChartLabelBadge.propTypes = {
entity: PropTypes.shape({
fieldName: PropTypes.string.isRequired,
fieldValue: PropTypes.string.isRequired
})
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 seriesConfig from '../../__mocks__/mock_series_config_filebeat.json';

import { shallow } from 'enzyme';
import React from 'react';

import { ExplorerChartLabelBadge } from './explorer_chart_label_badge';


describe('ExplorerChartLabelBadge', () => {

test('Render entity label badge.', () => {

const wrapper = shallow(<ExplorerChartLabelBadge entity={seriesConfig.entityFields[0]} />);
expect(wrapper).toMatchSnapshot();

});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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 * from './explorer_chart_label';
Loading

0 comments on commit 0ff498d

Please sign in to comment.