Basic hover to select metric

This commit is contained in:
Simon Howe
2016-03-08 16:47:45 +01:00
parent d31aadf7b1
commit ef1c69eb2a
11 changed files with 87 additions and 25 deletions

View File

@@ -12,6 +12,13 @@ import AppStore from '../stores/app-store';
const log = debug('scope:app-actions');
export function selectMetric(metricId) {
AppDispatcher.dispatch({
type: ActionTypes.SELECT_METRIC,
metricId: metricId
});
}
export function changeTopologyOption(option, value, topologyId) {
AppDispatcher.dispatch({
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,

View File

@@ -1,10 +1,10 @@
import React from 'react';
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
export default function NodeShapeCircle({highlighted, size, color, metrics}) {
export default function NodeShapeCircle({highlighted, size, color, metric}) {
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
const clipId = `mask-${Math.random()}`;
const {height, vp} = getMetricValue(metrics, size);
const {height, v} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -24,7 +24,7 @@ export default function NodeShapeCircle({highlighted, size, color, metrics}) {
<circle r={size * 0.45} className="metric-fill" clipPath={`url(#${clipId})`} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(vp)}
{formatCanvasMetric(v)}
</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>

View File

@@ -15,7 +15,7 @@ function polygon(r, sides) {
return points;
}
export default function NodeShapeHeptagon({highlighted, size, color, metrics}) {
export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
const scaledSize = size * 1.0;
const pathProps = v => ({
d: line(polygon(scaledSize * v, 7)),
@@ -23,7 +23,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metrics}) {
});
const clipId = `mask-${Math.random()}`;
const {height, vp} = getMetricValue(metrics, size);
const {height, v} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -43,7 +43,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metrics}) {
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(0.45)} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(vp)}
{formatCanvasMetric(v)}
</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>

View File

@@ -25,7 +25,7 @@ function getPoints(h) {
}
export default function NodeShapeHex({highlighted, size, color, metrics}) {
export default function NodeShapeHex({highlighted, size, color, metric}) {
const pathProps = v => ({
d: getPoints(size * v * 2),
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
@@ -35,7 +35,7 @@ export default function NodeShapeHex({highlighted, size, color, metrics}) {
const upperHexBitHeight = -0.25 * size * shadowSize;
const clipId = `mask-${Math.random()}`;
const {height, vp} = getMetricValue(metrics, size);
const {height, v} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -54,7 +54,7 @@ export default function NodeShapeHex({highlighted, size, color, metrics}) {
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(vp)}
{formatCanvasMetric(v)}
</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
export default function NodeShapeSquare({
highlighted, size, color, rx = 0, ry = 0, metrics
highlighted, size, color, rx = 0, ry = 0, metric
}) {
const rectProps = v => ({
width: v * size * 2,
@@ -14,7 +14,7 @@ export default function NodeShapeSquare({
});
const clipId = `mask-${Math.random()}`;
const {height, vp} = getMetricValue(metrics, size);
const {height, v} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -34,7 +34,7 @@ export default function NodeShapeSquare({
<rect className="metric-fill" clipPath={`url(#${clipId})`} {...rectProps(0.45)} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(vp)}
{formatCanvasMetric(v)}
</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>

View File

@@ -151,7 +151,7 @@ export default class NodesChart extends React.Component {
pseudo={node.get('pseudo')}
nodeCount={node.get('nodeCount')}
subLabel={node.get('subLabel')}
metrics={node.get('metrics')}
metric={node.getIn(['metrics', this.props.selectedMetric])}
rank={node.get('rank')}
selectedNodeScale={selectedNodeScale}
nodeScale={nodeScale}

View File

@@ -11,6 +11,7 @@ import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import { hitEsc } from '../actions/app-actions';
import Details from './details';
import Nodes from './nodes';
import MetricSelector from './metric-selector';
import EmbeddedTerminal from './embedded-terminal';
import { getRouter } from '../utils/router-utils';
import { showingDebugToolbar, DebugToolbar } from './debug-toolbar.js';
@@ -33,6 +34,7 @@ function getStateFromStores() {
nodeDetails: AppStore.getNodeDetails(),
nodes: AppStore.getNodes(),
selectedNodeId: AppStore.getSelectedNodeId(),
selectedMetric: AppStore.getSelectedMetric(),
topologies: AppStore.getTopologies(),
topologiesLoaded: AppStore.isTopologiesLoaded(),
updatePaused: AppStore.isUpdatePaused(),
@@ -103,14 +105,20 @@ export default class App extends React.Component {
currentTopology={this.state.currentTopology} />
</div>
<Nodes nodes={this.state.nodes} highlightedNodeIds={this.state.highlightedNodeIds}
highlightedEdgeIds={this.state.highlightedEdgeIds} detailsWidth={detailsWidth}
selectedNodeId={this.state.selectedNodeId} topMargin={topMargin}
<Nodes
nodes={this.state.nodes}
highlightedNodeIds={this.state.highlightedNodeIds}
highlightedEdgeIds={this.state.highlightedEdgeIds}
detailsWidth={detailsWidth}
selectedNodeId={this.state.selectedNodeId}
topMargin={topMargin}
selectedMetric={this.state.selectedMetric}
forceRelayout={this.state.forceRelayout}
topologyOptions={this.state.activeTopologyOptions}
topologyId={this.state.currentTopologyId} />
<Sidebar>
<MetricSelector selectedMetric={this.state.selectedMetric}/>
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
topologiesLoaded={this.state.topologiesLoaded}
websocketClosed={this.state.websocketClosed} />

View File

@@ -0,0 +1,36 @@
import React from 'react';
import _ from 'lodash';
import { selectMetric } from '../actions/app-actions';
import classNames from 'classnames';
const METRICS = {
'CPU': 'process_cpu_usage_percent',
'Memory': 'process_memory_usage_bytes',
'Open Files': 'open_files_count'
};
// docker_cpu_total_usage
// docker_memory_usage
function onMouseOver(k) {
return selectMetric(k);
}
export default function MetricSelector({selectedMetric}) {
return (
<div className="available-metrics">
{_.map(METRICS, (key, name) => {
return (
<div
key={key}
className={classNames('sidebar-item', {
'selected': (key === selectedMetric)
})}
onMouseOver={() => onMouseOver(key)}>
{name}
</div>
);
})}
</div>
);
}

View File

@@ -33,7 +33,8 @@ const ACTION_TYPES = [
'RECEIVE_TOPOLOGIES',
'RECEIVE_API_DETAILS',
'RECEIVE_ERROR',
'ROUTE_TOPOLOGY'
'ROUTE_TOPOLOGY',
'SELECT_METRIC'
];
export default _.zipObject(ACTION_TYPES, ACTION_TYPES);

View File

@@ -58,6 +58,7 @@ let routeSet = false;
let controlPipes = makeOrderedMap(); // pipeId -> controlPipe
let updatePausedAt = null; // Date
let websocketClosed = true;
let selectedMetric = 'process_cpu_usage_percent';
const topologySorter = topology => topology.get('rank');
@@ -164,6 +165,10 @@ export class AppStore extends Store {
return adjacentNodes;
}
getSelectedMetric() {
return selectedMetric;
}
getControlStatus() {
return controlStatus.toJS();
}
@@ -401,6 +406,11 @@ export class AppStore extends Store {
}
break;
}
case ActionTypes.SELECT_METRIC: {
selectedMetric = payload.metricId;
this.__emitChange();
break;
}
case ActionTypes.DESELECT_NODE: {
closeNodeDetails();
this.__emitChange();

View File

@@ -78,19 +78,19 @@ export function addMetrics(delta, prevNodes) {
}
export function getMetricValue(metrics, size) {
if (!metrics) {
return {height: 0, vp: null};
export function getMetricValue(metric, size) {
if (!metric) {
return {height: 0, v: null};
}
const max = 100;
const v = metrics.getIn(['process_cpu_usage_percent', 'samples', 0, 'value']);
const max = metric.getIn(['max']);
const v = metric.getIn(['samples', 0, 'value']);
const vp = v === 0 ? 0 : v / max;
const baseline = 0.00;
const baseline = 0.05;
const displayedValue = vp * (1 - baseline) + baseline;
const height = size * displayedValue;
return {height, vp};
return {height, v};
}
const formatLargeValue = d3.format('s');
@@ -98,5 +98,5 @@ export function formatCanvasMetric(v) {
if (v === null) {
return 'n/a';
}
return formatLargeValue(Number(v * 100).toFixed(1)) + '%';
return formatLargeValue(Number(v).toFixed(1));
}