From 5a2593b5eb9d3e4eab56fc8cc1bde4f8f33dc516 Mon Sep 17 00:00:00 2001 From: Roland Schilter Date: Tue, 11 Jul 2017 14:05:12 +0200 Subject: [PATCH] New design for hover states and overflow handling --- client/app/scripts/components/node-details.js | 36 +++++++++++++------ .../node-details/node-details-health-item.js | 7 ++-- .../node-details-health-link-item.js | 35 +++++++++++++++--- .../node-details-health-overflow-item.js | 2 +- .../node-details-health-overflow.js | 2 +- .../node-details/node-details-health.js | 14 ++------ client/app/scripts/utils/metric-utils.js | 4 ++- client/app/styles/_base.scss | 3 ++ 8 files changed, 69 insertions(+), 34 deletions(-) diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js index 399e2b6d1..dc1a6e8e7 100644 --- a/client/app/scripts/components/node-details.js +++ b/client/app/scripts/components/node-details.js @@ -54,6 +54,28 @@ class NodeDetails extends React.Component { resetDocumentTitle(); } + static collectMetrics(details) { + const metrics = details.metrics || []; + + // collect by metric id (id => link) + const metricLinks = (details.metric_links || []) + .reduce((agg, link) => Object.assign(agg, {[link.id]: link}), {}); + + const availableMetrics = metrics.reduce( + (agg, m) => Object.assign(agg, {[m.id]: true}), + {} + ); + + // append links with no metrics as fake metrics + (details.metric_links || []).forEach((link) => { + if (availableMetrics[link.id] === undefined) { + metrics.push({id: link.id, label: link.label}); + } + }); + + return { metrics, metricLinks }; + } + renderTools() { const showSwitchTopology = this.props.nodeId !== this.props.selectedNodeId; const topologyTitle = `View ${this.props.label} in ${this.props.topologyId}`; @@ -165,13 +187,7 @@ class NodeDetails extends React.Component { } }; - // collect by metric id (id => link) - const metricLinks = (details.metric_links || []) - .reduce((agg, link) => Object.assign(agg, {[link.id]: link}), {}); - - // collect links with no corresponding metric - const unattachedLinks = Object.assign({}, metricLinks); - (details.metrics || []).forEach(metric => delete unattachedLinks[metric.id]); + const { metrics, metricLinks } = NodeDetails.collectMetrics(details); return (
@@ -198,13 +214,13 @@ class NodeDetails extends React.Component {
}
- {((details.metrics || []).length + Object.keys(unattachedLinks).length > 0) &&
+ {metrics.length > 0 &&
Status
} {details.metadata &&
diff --git a/client/app/scripts/components/node-details/node-details-health-item.js b/client/app/scripts/components/node-details/node-details-health-item.js index fe572591c..89cb1131a 100644 --- a/client/app/scripts/components/node-details/node-details-health-item.js +++ b/client/app/scripts/components/node-details/node-details-health-item.js @@ -10,12 +10,11 @@ function NodeDetailsHealthItem(props) { {props.samples &&
+ first={props.first} last={props.last} strokeWidth={props.strokeWidth} + strokeColor={props.strokeColor} />
} - {!props.samples &&
} -
+
{props.label} - {props.icon && }
); diff --git a/client/app/scripts/components/node-details/node-details-health-link-item.js b/client/app/scripts/components/node-details/node-details-health-link-item.js index be020e393..690ba5854 100644 --- a/client/app/scripts/components/node-details/node-details-health-link-item.js +++ b/client/app/scripts/components/node-details/node-details-health-link-item.js @@ -1,15 +1,29 @@ import React from 'react'; import { trackMixpanelEvent } from '../../utils/tracking-utils'; +import { getMetricColor } from '../../utils/metric-utils'; import NodeDetailsHealthItem from './node-details-health-item'; export default class NodeDetailsHealthLinkItem extends React.Component { constructor(props, context) { super(props, context); + this.state = { + hovered: false + }; this.handleClick = this.handleClick.bind(this); this.buildHref = this.buildHref.bind(this); + this.onMouseOver = this.onMouseOver.bind(this); + this.onMouseOut = this.onMouseOut.bind(this); + } + + onMouseOver() { + this.setState({hovered: true}); + } + + onMouseOut() { + this.setState({hovered: false}); } handleClick(ev, href) { @@ -32,18 +46,29 @@ export default class NodeDetailsHealthLinkItem extends React.Component { } render() { - const { links, withoutGraph, id, ...props } = this.props; + const { links, id, nodeColor, ...props } = this.props; const href = this.buildHref(links[id] && links[id].url); - if (!href) return ; + const hasData = (props.samples && props.samples.length > 0) || props.value !== undefined; + const labelColor = this.state.hovered && !hasData ? nodeColor : undefined; + const sparkline = {}; + if (this.state.hovered) { + sparkline.strokeColor = getMetricColor(id); + sparkline.strokeWidth = '2px'; + } + return ( this.handleClick(e, href)}> - {!withoutGraph && } - {withoutGraph && } + onClick={e => this.handleClick(e, href)} + onMouseOver={this.onMouseOver} + onMouseOut={this.onMouseOut}> + ); } diff --git a/client/app/scripts/components/node-details/node-details-health-overflow-item.js b/client/app/scripts/components/node-details/node-details-health-overflow-item.js index 6f69bd2fa..36c6ff6de 100644 --- a/client/app/scripts/components/node-details/node-details-health-overflow-item.js +++ b/client/app/scripts/components/node-details/node-details-health-overflow-item.js @@ -6,7 +6,7 @@ function NodeDetailsHealthOverflowItem(props) { return (
- {formatMetric(props.value, props)} + {props.value !== undefined && formatMetric(props.value, props)}
{props.label}
diff --git a/client/app/scripts/components/node-details/node-details-health-overflow.js b/client/app/scripts/components/node-details/node-details-health-overflow.js index 76fcea7f8..537c3a695 100644 --- a/client/app/scripts/components/node-details/node-details-health-overflow.js +++ b/client/app/scripts/components/node-details/node-details-health-overflow.js @@ -18,7 +18,7 @@ export default class NodeDetailsHealthOverflow extends React.Component { const items = this.props.items.slice(0, 4); return ( -
+
{items.map(item => )}
); diff --git a/client/app/scripts/components/node-details/node-details-health.js b/client/app/scripts/components/node-details/node-details-health.js index b2648fa25..e323d75bb 100644 --- a/client/app/scripts/components/node-details/node-details-health.js +++ b/client/app/scripts/components/node-details/node-details-health.js @@ -25,8 +25,8 @@ export default class NodeDetailsHealth extends React.Component { const { metrics = makeList(), metricLinks = makeMap(), - unattachedLinks = makeMap(), topologyId, + nodeColor, } = this.props; const primeCutoff = metrics.length > 3 && !this.state.expanded ? 2 : metrics.length; @@ -45,6 +45,7 @@ export default class NodeDetailsHealth extends React.Component { {...item} links={metricLinks} topologyId={topologyId} + nodeColor={nodeColor} /> )} {showOverflow && - -
- {Object.keys(unattachedLinks).map(id => - - )} -
); } diff --git a/client/app/scripts/utils/metric-utils.js b/client/app/scripts/utils/metric-utils.js index bf14432e8..4350bddb6 100644 --- a/client/app/scripts/utils/metric-utils.js +++ b/client/app/scripts/utils/metric-utils.js @@ -52,7 +52,9 @@ export function getMetricValue(metric) { export function getMetricColor(metric) { - const metricId = metric && metric.get('id'); + const metricId = typeof metric === 'string' + ? metric + : metric && metric.get('id'); if (/mem/.test(metricId)) { return 'steelBlue'; } else if (/cpu/.test(metricId)) { diff --git a/client/app/styles/_base.scss b/client/app/styles/_base.scss index a62fd53d0..3cacf9200 100644 --- a/client/app/styles/_base.scss +++ b/client/app/styles/_base.scss @@ -926,11 +926,14 @@ &-item { padding: 8px 16px; width: 33%; + display: flex; + flex-direction: column; &-label { color: $text-secondary-color; text-transform: uppercase; font-size: 80%; + margin-top: auto; .fa { margin-left: 0.5em;