From ef1c69eb2ae2da07af1b988b7205425e7a1d64bb Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Tue, 8 Mar 2016 16:47:45 +0100 Subject: [PATCH] Basic hover to select metric --- client/app/scripts/actions/app-actions.js | 7 ++++ .../app/scripts/charts/node-shape-circle.js | 6 ++-- .../app/scripts/charts/node-shape-heptagon.js | 6 ++-- client/app/scripts/charts/node-shape-hex.js | 6 ++-- .../app/scripts/charts/node-shape-square.js | 6 ++-- client/app/scripts/charts/nodes-chart.js | 2 +- client/app/scripts/components/app.js | 14 ++++++-- .../app/scripts/components/metric-selector.js | 36 +++++++++++++++++++ client/app/scripts/constants/action-types.js | 3 +- client/app/scripts/stores/app-store.js | 10 ++++++ client/app/scripts/utils/data-utils.js | 16 ++++----- 11 files changed, 87 insertions(+), 25 deletions(-) create mode 100644 client/app/scripts/components/metric-selector.js diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 7387cbc10..c780fb0e8 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -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, diff --git a/client/app/scripts/charts/node-shape-circle.js b/client/app/scripts/charts/node-shape-circle.js index 53d0c7dcd..c234e02a8 100644 --- a/client/app/scripts/charts/node-shape-circle.js +++ b/client/app/scripts/charts/node-shape-circle.js @@ -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 = ; const clipId = `mask-${Math.random()}`; - const {height, vp} = getMetricValue(metrics, size); + const {height, v} = getMetricValue(metric, size); return ( @@ -24,7 +24,7 @@ export default function NodeShapeCircle({highlighted, size, color, metrics}) { {highlighted ? - {formatCanvasMetric(vp)} + {formatCanvasMetric(v)} : } diff --git a/client/app/scripts/charts/node-shape-heptagon.js b/client/app/scripts/charts/node-shape-heptagon.js index 41e1ce189..84a7324aa 100644 --- a/client/app/scripts/charts/node-shape-heptagon.js +++ b/client/app/scripts/charts/node-shape-heptagon.js @@ -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 ( @@ -43,7 +43,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metrics}) { {highlighted ? - {formatCanvasMetric(vp)} + {formatCanvasMetric(v)} : } diff --git a/client/app/scripts/charts/node-shape-hex.js b/client/app/scripts/charts/node-shape-hex.js index dd4ab6cfb..f558c812a 100644 --- a/client/app/scripts/charts/node-shape-hex.js +++ b/client/app/scripts/charts/node-shape-hex.js @@ -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 ( @@ -54,7 +54,7 @@ export default function NodeShapeHex({highlighted, size, color, metrics}) { {highlighted ? - {formatCanvasMetric(vp)} + {formatCanvasMetric(v)} : } diff --git a/client/app/scripts/charts/node-shape-square.js b/client/app/scripts/charts/node-shape-square.js index 2c28e82bd..5da8184a3 100644 --- a/client/app/scripts/charts/node-shape-square.js +++ b/client/app/scripts/charts/node-shape-square.js @@ -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 ( @@ -34,7 +34,7 @@ export default function NodeShapeSquare({ {highlighted ? - {formatCanvasMetric(vp)} + {formatCanvasMetric(v)} : } diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index a9e411b06..81ec16a25 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -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} diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index d2e37fa58..cbf0ecd9c 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -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} /> - + diff --git a/client/app/scripts/components/metric-selector.js b/client/app/scripts/components/metric-selector.js new file mode 100644 index 000000000..b76da5109 --- /dev/null +++ b/client/app/scripts/components/metric-selector.js @@ -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 ( +
+ {_.map(METRICS, (key, name) => { + return ( +
onMouseOver(key)}> + {name} +
+ ); + })} +
+ ); +} diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index 047dc8274..87aac25b9 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -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); diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js index ebfef2e41..8e3115666 100644 --- a/client/app/scripts/stores/app-store.js +++ b/client/app/scripts/stores/app-store.js @@ -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(); diff --git a/client/app/scripts/utils/data-utils.js b/client/app/scripts/utils/data-utils.js index 600abaffc..d7276ce51 100644 --- a/client/app/scripts/utils/data-utils.js +++ b/client/app/scripts/utils/data-utils.js @@ -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)); }