mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Simple metric selection clicking!
Locks onto a metric after you mouseout.
This commit is contained in:
@@ -19,6 +19,13 @@ export function selectMetric(metricId) {
|
||||
});
|
||||
}
|
||||
|
||||
export function lockMetric(metricId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.LOCK_METRIC,
|
||||
metricId: metricId
|
||||
});
|
||||
}
|
||||
|
||||
export function changeTopologyOption(option, value, topologyId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
|
||||
import {getMetricValue} from '../utils/data-utils.js';
|
||||
|
||||
export default function NodeShapeCircle({highlighted, size, color, metric}) {
|
||||
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -23,9 +23,7 @@ export default function NodeShapeCircle({highlighted, size, color, metric}) {
|
||||
<circle r={size * 0.45} className="shadow" />
|
||||
<circle r={size * 0.45} className="metric-fill" clipPath={`url(#${clipId})`} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
|
||||
import {getMetricValue} from '../utils/data-utils.js';
|
||||
|
||||
const line = d3.svg.line()
|
||||
.interpolate('cardinal-closed')
|
||||
@@ -23,7 +23,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -42,9 +42,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
|
||||
<path className="shadow" {...pathProps(0.45)} />
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(0.45)} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
|
||||
import {getMetricValue} from '../utils/data-utils.js';
|
||||
|
||||
const line = d3.svg.line()
|
||||
.interpolate('cardinal-closed')
|
||||
@@ -35,7 +35,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
const upperHexBitHeight = -0.25 * size * shadowSize;
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -54,7 +54,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(v)}
|
||||
{formattedValue}
|
||||
</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
|
||||
import {getMetricValue} from '../utils/data-utils.js';
|
||||
|
||||
export default function NodeShapeSquare({
|
||||
highlighted, size, color, rx = 0, ry = 0, metric
|
||||
@@ -14,7 +14,7 @@ export default function NodeShapeSquare({
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -33,9 +33,7 @@ export default function NodeShapeSquare({
|
||||
<rect className="shadow" {...rectProps(0.45)} />
|
||||
<rect className="metric-fill" clipPath={`url(#${clipId})`} {...rectProps(0.45)} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ function getStateFromStores() {
|
||||
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
|
||||
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
|
||||
hostname: AppStore.getHostname(),
|
||||
lockedMetric: AppStore.getLockedMetric(),
|
||||
nodeDetails: AppStore.getNodeDetails(),
|
||||
nodes: AppStore.getNodes(),
|
||||
selectedNodeId: AppStore.getSelectedNodeId(),
|
||||
@@ -118,7 +119,10 @@ export default class App extends React.Component {
|
||||
topologyId={this.state.currentTopologyId} />
|
||||
|
||||
<Sidebar>
|
||||
<MetricSelector selectedMetric={this.state.selectedMetric}/>
|
||||
<MetricSelector
|
||||
lockedMetric={this.state.lockedMetric}
|
||||
selectedMetric={this.state.selectedMetric}
|
||||
/>
|
||||
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
|
||||
topologiesLoaded={this.state.topologiesLoaded}
|
||||
websocketClosed={this.state.websocketClosed} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { selectMetric } from '../actions/app-actions';
|
||||
import { selectMetric, lockMetric } from '../actions/app-actions';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const METRICS = {
|
||||
@@ -16,17 +16,30 @@ function onMouseOver(k) {
|
||||
return selectMetric(k);
|
||||
}
|
||||
|
||||
export default function MetricSelector({selectedMetric}) {
|
||||
function onMouseClick(k) {
|
||||
return lockMetric(k);
|
||||
}
|
||||
|
||||
function onMouseOut(k) {
|
||||
console.log('onMouseOut', k);
|
||||
selectMetric(k);
|
||||
}
|
||||
|
||||
export default function MetricSelector({selectedMetric, lockedMetric}) {
|
||||
return (
|
||||
<div className="available-metrics">
|
||||
<div
|
||||
className="available-metrics"
|
||||
onMouseLeave={() => onMouseOut(lockedMetric)}>
|
||||
{_.map(METRICS, (key, name) => {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={classNames('sidebar-item', {
|
||||
'locked': (key === lockedMetric),
|
||||
'selected': (key === selectedMetric)
|
||||
})}
|
||||
onMouseOver={() => onMouseOver(key)}>
|
||||
onMouseOver={() => onMouseOver(key)}
|
||||
onClick={() => onMouseClick(key)}>
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ const ACTION_TYPES = [
|
||||
'ENTER_NODE',
|
||||
'LEAVE_EDGE',
|
||||
'LEAVE_NODE',
|
||||
'LOCK_METRIC',
|
||||
'OPEN_WEBSOCKET',
|
||||
'RECEIVE_CONTROL_PIPE',
|
||||
'RECEIVE_CONTROL_PIPE_STATUS',
|
||||
|
||||
@@ -58,7 +58,9 @@ let routeSet = false;
|
||||
let controlPipes = makeOrderedMap(); // pipeId -> controlPipe
|
||||
let updatePausedAt = null; // Date
|
||||
let websocketClosed = true;
|
||||
|
||||
let selectedMetric = 'process_cpu_usage_percent';
|
||||
let lockedMetric = selectedMetric;
|
||||
|
||||
const topologySorter = topology => topology.get('rank');
|
||||
|
||||
@@ -165,6 +167,10 @@ export class AppStore extends Store {
|
||||
return adjacentNodes;
|
||||
}
|
||||
|
||||
getLockedMetric() {
|
||||
return lockedMetric;
|
||||
}
|
||||
|
||||
getSelectedMetric() {
|
||||
return selectedMetric;
|
||||
}
|
||||
@@ -411,6 +417,11 @@ export class AppStore extends Store {
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.LOCK_METRIC: {
|
||||
lockedMetric = payload.metricId;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DESELECT_NODE: {
|
||||
closeNodeDetails();
|
||||
this.__emitChange();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import { formatMetric } from './string-utils';
|
||||
|
||||
|
||||
// Inspired by Lee Byron's test data generator.
|
||||
@@ -80,23 +81,19 @@ export function addMetrics(delta, prevNodes) {
|
||||
|
||||
export function getMetricValue(metric, size) {
|
||||
if (!metric) {
|
||||
return {height: 0, v: null};
|
||||
return {height: 0, value: null, formattedValue: 'n/a'};
|
||||
}
|
||||
|
||||
const max = metric.getIn(['max']);
|
||||
const v = metric.getIn(['samples', 0, 'value']);
|
||||
const vp = v === 0 ? 0 : v / max;
|
||||
const value = metric.getIn(['samples', 0, 'value']);
|
||||
const valuePercentage = value === 0 ? 0 : value / max;
|
||||
const baseline = 0.05;
|
||||
const displayedValue = vp * (1 - baseline) + baseline;
|
||||
const displayedValue = valuePercentage * (1 - baseline) + baseline;
|
||||
const height = size * displayedValue;
|
||||
|
||||
return {height, v};
|
||||
}
|
||||
|
||||
const formatLargeValue = d3.format('s');
|
||||
export function formatCanvasMetric(v) {
|
||||
if (v === null) {
|
||||
return 'n/a';
|
||||
}
|
||||
return formatLargeValue(Number(v).toFixed(1));
|
||||
return {
|
||||
height: height,
|
||||
value: value,
|
||||
formattedValue: formatMetric(value, metric.toJS(), true)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,43 +4,54 @@ import d3 from 'd3';
|
||||
|
||||
const formatLargeValue = d3.format('s');
|
||||
|
||||
const formatters = {
|
||||
filesize(value) {
|
||||
const obj = filesize(value, {output: 'object'});
|
||||
return formatters.metric(obj.value, obj.suffix);
|
||||
},
|
||||
function toHtml(text, unit) {
|
||||
return (
|
||||
<span className="metric-formatted">
|
||||
<span className="metric-value">{text}</span>
|
||||
<span className="metric-unit">{unit}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
integer(value) {
|
||||
if (value < 1100 && value >= 0) {
|
||||
return Number(value).toFixed(0);
|
||||
|
||||
function makeFormatters(renderFn) {
|
||||
const formatters = {
|
||||
filesize(value) {
|
||||
const obj = filesize(value, {output: 'object'});
|
||||
console.log('rendering', value);
|
||||
return renderFn(obj.value, obj.suffix);
|
||||
},
|
||||
|
||||
integer(value) {
|
||||
if (value < 1100 && value >= 0) {
|
||||
return Number(value).toFixed(0);
|
||||
}
|
||||
return formatLargeValue(value);
|
||||
},
|
||||
|
||||
number(value) {
|
||||
if (value < 1100 && value >= 0) {
|
||||
return Number(value).toFixed(2);
|
||||
}
|
||||
return formatLargeValue(value);
|
||||
},
|
||||
|
||||
percent(value) {
|
||||
return renderFn(formatters.number(value), '%');
|
||||
}
|
||||
return formatLargeValue(value);
|
||||
},
|
||||
};
|
||||
|
||||
number(value) {
|
||||
if (value < 1100 && value >= 0) {
|
||||
return Number(value).toFixed(2);
|
||||
}
|
||||
return formatLargeValue(value);
|
||||
},
|
||||
return formatters;
|
||||
}
|
||||
|
||||
percent(value) {
|
||||
return formatters.metric(formatters.number(value), '%');
|
||||
},
|
||||
|
||||
metric(text, unit) {
|
||||
return (
|
||||
<span className="metric-formatted">
|
||||
<span className="metric-value">{text}</span>
|
||||
<span className="metric-unit">{unit}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
const formatters = makeFormatters(toHtml);
|
||||
const svgFormatters = makeFormatters((text, unit) => `${text}${unit}`);
|
||||
|
||||
export function formatMetric(value, opts) {
|
||||
const formatter = opts && formatters[opts.format] ? opts.format : 'number';
|
||||
return formatters[formatter](value);
|
||||
export function formatMetric(value, opts, svg) {
|
||||
const formatterBase = svg ? svgFormatters : formatters;
|
||||
const formatter = opts && formatterBase[opts.format] ? opts.format : 'number';
|
||||
return formatterBase[formatter](value);
|
||||
}
|
||||
|
||||
export const formatDate = d3.time.format.iso;
|
||||
|
||||
Reference in New Issue
Block a user