Skip to content

Commit

Permalink
Append links directly to metrics
Browse files Browse the repository at this point in the history
The initial idea was to keep it separate since the unattached
links were also to be displayed distinctively from the metrics.
With the new design, unattached links are rendered in the same
list as metrics with attached links.

Therefore, we treat unattached metric links as an empty metric.
  • Loading branch information
rndstr committed Jul 24, 2017
1 parent 9b2d16a commit 388a599
Show file tree
Hide file tree
Showing 23 changed files with 511 additions and 283 deletions.
1 change: 1 addition & 0 deletions client/app/scripts/charts/nodes-grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function getColumns(nodes) {
.toList()
.flatMap((n) => {
const metrics = (n.get('metrics') || makeList())
.filter(m => !m.get('valueEmpty'))
.map(m => makeMap({ id: m.get('id'), label: m.get('label'), dataType: 'number' }));
return metrics;
})
Expand Down
5 changes: 2 additions & 3 deletions client/app/scripts/components/cloud-feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ class CloudFeature extends React.Component {
if (process.env.WEAVE_CLOUD) {
return React.cloneElement(React.Children.only(this.props.children), {
params: this.context.router.params,
router: this.context.router,
isCloud: true
router: this.context.router
});
}

// also show if not in weave cloud?
if (this.props.alwaysShow) {
return React.cloneElement(React.Children.only(this.props.children), {isCloud: false});
return React.cloneElement(React.Children.only(this.props.children));
}

return null;
Expand Down
72 changes: 72 additions & 0 deletions client/app/scripts/components/cloud-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { connect } from 'react-redux';
import filterInvalidDOMProps from 'filter-invalid-dom-props';

import CloudFeature from './cloud-feature';

/**
* CloudLink provides an anchor that allows to set a target
* that is comprised of Weave Cloud related pieces.
*
* We support here relative links with a leading `/` that rewrite
* the browser url as well as cloud-related placeholders (:orgId).
*
* If no `url` is given, only the children is rendered (no anchor).
*
* If you want to render the content even if not on the cloud, set
* the `alwaysShow` property. A location redirect will be made for
* clicks instead.
*/
const CloudLink = ({ alwaysShow, ...props }) => (
<CloudFeature alwaysShow={alwaysShow}>
<LinkWrapper {...props} />
</CloudFeature>
);

class LinkWrapper extends React.Component {
constructor(props, context) {
super(props, context);

this.handleClick = this.handleClick.bind(this);
this.buildHref = this.buildHref.bind(this);
}

handleClick(ev, href) {
ev.preventDefault();
if (!href) return;

const { router, onClick } = this.props;

if (onClick) {
onClick();
}

if (router && href[0] === '/') {
router.push(href);
} else {
location.href = href;
}
}

buildHref(url) {
const { params } = this.props;
if (!url || !params || !params.orgId) return url;
return url.replace(/:orgid/gi, encodeURIComponent(this.props.params.orgId));
}

render() {
const { url, children, ...props } = this.props;
if (!url) {
return children;
}

const href = this.buildHref(url);
return (
<a {...filterInvalidDOMProps(props)} href={href} onClick={e => this.handleClick(e, href)}>
{children}
</a>
);
}
}

export default connect()(CloudLink);
29 changes: 2 additions & 27 deletions client/app/scripts/components/node-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,6 @@ 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}`;
Expand Down Expand Up @@ -187,8 +165,6 @@ class NodeDetails extends React.Component {
}
};

const { metrics, metricLinks } = NodeDetails.collectMetrics(details);

return (
<div className="node-details">
{tools}
Expand All @@ -214,11 +190,10 @@ class NodeDetails extends React.Component {
</div>}

<div className="node-details-content">
{metrics.length > 0 && <div className="node-details-content-section">
{details.metrics && <div className="node-details-content-section">
<div className="node-details-content-section-header">Status</div>
<NodeDetailsHealth
metrics={metrics}
metricLinks={metricLinks}
metrics={details.metrics}
topologyId={topologyId}
nodeColor={nodeColor}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { formatMetric } from '../../utils/string-utils';
function NodeDetailsHealthItem(props) {
return (
<div className="node-details-health-item">
{props.value !== undefined && <div className="node-details-health-item-value">{formatMetric(props.value, props)}</div>}
{props.samples && <div className="node-details-health-item-sparkline">
{!props.valueEmpty && <div className="node-details-health-item-value">{formatMetric(props.value, props)}</div>}
<div className="node-details-health-item-sparkline">
<Sparkline
data={props.samples} max={props.max} format={props.format}
first={props.first} last={props.last} strokeWidth={props.strokeWidth}
strokeColor={props.strokeColor} />
</div>}
first={props.first} last={props.last} hoverColor={props.metricColor}
hovered={props.samples && props.hovered}
/>
</div>
<div className="node-details-health-item-label" style={{ color: props.labelColor }}>
{props.label}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import React from 'react';

import { trackMixpanelEvent } from '../../utils/tracking-utils';
import { getMetricColor } from '../../utils/metric-utils';
import NodeDetailsHealthItem from './node-details-health-item';
import CloudLink from '../cloud-link';
import { getMetricColor } from '../../utils/metric-utils';
import { trackMixpanelEvent } from '../../utils/tracking-utils';

export default class NodeDetailsHealthLinkItem extends React.Component {

constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
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);
this.onClick = this.onClick.bind(this);
}

onMouseOver() {
Expand All @@ -26,50 +26,31 @@ export default class NodeDetailsHealthLinkItem extends React.Component {
this.setState({hovered: false});
}

handleClick(ev, href) {
ev.preventDefault();
if (!href) return;

const { router, topologyId } = this.props;
trackMixpanelEvent('scope.node.health.click', { topologyId });

if (router && href[0] === '/') {
router.push(href);
} else {
location.href = href;
}
}

buildHref(url) {
if (!url || !this.props.isCloud) return url;
return url.replace(/:orgid/gi, encodeURIComponent(this.props.params.orgId));
onClick() {
trackMixpanelEvent('scope.node.metric.click', { topologyId: this.props.topologyId });
}

render() {
const { links, id, nodeColor, ...props } = this.props;
const href = this.buildHref(links[id] && links[id].url);
if (!href) return <NodeDetailsHealthItem {...props} />;
const { id, nodeColor, url, ...props } = this.props;

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';
}
const labelColor = this.state.hovered && !props.samples ? nodeColor : undefined;
const metricColor = getMetricColor(id);

return (
<a
<CloudLink
alwaysShow
className="node-details-health-link-item"
href={href}
onClick={e => this.handleClick(e, href)}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}>
onMouseOut={this.onMouseOut}
onClick={this.onClick}
url={url}
>
<NodeDetailsHealthItem
{...props}
{...sparkline}
hovered={this.state.hovered}
metricColor={metricColor}
labelColor={labelColor} />
</a>
</CloudLink>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function NodeDetailsHealthOverflowItem(props) {
return (
<div className="node-details-health-overflow-item">
<div className="node-details-health-overflow-item-value">
{props.value !== undefined && formatMetric(props.value, props)}
{!props.valueEmpty && formatMetric(props.value, props)}
</div>
<div className="node-details-health-overflow-item-label truncate">{props.label}</div>
</div>
Expand Down
18 changes: 7 additions & 11 deletions client/app/scripts/components/node-details/node-details-health.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from 'react';
import { Map as makeMap, List as makeList } from 'immutable';
import { List as makeList } from 'immutable';

import ShowMore from '../show-more';
import NodeDetailsHealthOverflow from './node-details-health-overflow';
import NodeDetailsHealthLinkItem from './node-details-health-link-item';
import CloudFeature from '../cloud-feature';

export default class NodeDetailsHealth extends React.Component {

Expand All @@ -24,7 +23,6 @@ export default class NodeDetailsHealth extends React.Component {
render() {
const {
metrics = makeList(),
metricLinks = makeMap(),
topologyId,
nodeColor,
} = this.props;
Expand All @@ -40,14 +38,12 @@ export default class NodeDetailsHealth extends React.Component {
return (
<div className="node-details-health" style={{flexWrap, justifyContent}}>
<div className="node-details-health-wrapper">
{primeMetrics.map(item => <CloudFeature alwaysShow key={item.id}>
<NodeDetailsHealthLinkItem
{...item}
links={metricLinks}
topologyId={topologyId}
nodeColor={nodeColor}
/>
</CloudFeature>)}
{primeMetrics.map(item => <NodeDetailsHealthLinkItem
{...item}
key={item.id}
topologyId={topologyId}
nodeColor={nodeColor}
/>)}
{showOverflow && <NodeDetailsHealthOverflow
items={overflowMetrics}
handleClick={this.handleClickMore}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';

import CloudLink from '../cloud-link';
import { formatMetric } from '../../utils/string-utils';
import { trackMixpanelEvent } from '../../utils/tracking-utils';

class NodeDetailsTableNodeMetricLink extends React.Component {
constructor(props) {
super(props);

this.onClick = this.onClick.bind(this);
}

onClick() {
trackMixpanelEvent('scope.node.metric.click', { topologyId: this.props.topologyId });
}

static dismissEvent(ev) {
ev.preventDefault();
ev.stopPropagation();
}

render() {
const { url, style, value, valueEmpty } = this.props;

return (
<td
className="node-details-table-node-metric"
style={style}
// Skip onMouseUp event for table row
onMouseUp={NodeDetailsTableNodeMetricLink.dismissEvent}
>
<CloudLink
alwaysShow
url={url}
className={url && 'node-details-table-node-metric-link'}
onClick={this.onClick}
>
{!valueEmpty && formatMetric(value, this.props)}
</CloudLink>
</td>
);
}
}

export default NodeDetailsTableNodeMetricLink;

This file was deleted.

Loading

0 comments on commit 388a599

Please sign in to comment.