From 658a38f40a2649b6170dfd6929649423ec3e5801 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 15 Mar 2016 13:36:53 +0100 Subject: [PATCH 1/2] Upgrade npm app dependencies --- client/app/scripts/charts/edge.js | 2 +- client/app/scripts/charts/node.js | 2 +- .../node-details/node-details-table.js | 2 +- client/app/scripts/stores/app-store.js | 2 +- client/package.json | 22 +++++++++---------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js index 665bee48f8..a2f51e8950 100644 --- a/client/app/scripts/charts/edge.js +++ b/client/app/scripts/charts/edge.js @@ -10,7 +10,7 @@ const line = d3.svg.line() .x(function(d) { return d.x; }) .y(function(d) { return d.y; }); -const animConfig = [80, 20];// stiffness, bounce +const animConfig = {stiffness: 80, damping: 20}; const flattenPoints = function(points) { const flattened = {}; diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index 3413f23953..43be883130 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -62,7 +62,7 @@ export default class Node extends React.Component { const onMouseLeave = this.handleMouseLeave; const onMouseClick = this.handleMouseClick; const classNames = ['node']; - const animConfig = [80, 20]; // stiffness, bounce + const animConfig = {stiffness: 80, damping: 20}; const label = this.ellipsis(props.label, 14, nodeScale(4 * scaleFactor)); const subLabel = this.ellipsis(props.subLabel, 12, nodeScale(4 * scaleFactor)); let labelFontSize = 14; diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js index 82303eb585..ca259f017c 100644 --- a/client/app/scripts/components/node-details/node-details-table.js +++ b/client/app/scripts/components/node-details/node-details-table.js @@ -145,7 +145,7 @@ export default class NodeDetailsTable extends React.Component { render() { const headers = this.renderHeaders(); - let nodes = _.sortByAll(this.props.nodes, this.getValueForSortBy, 'label', this.getMetaDataSorters()); + let nodes = _.sortBy(this.props.nodes, this.getValueForSortBy, 'label', this.getMetaDataSorters()); const limited = nodes && this.state.limit > 0 && nodes.length > this.state.limit; const expanded = this.state.limit === 0; const notShown = nodes.length - this.DEFAULT_LIMIT; diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js index 0bfea9c214..20edade72d 100644 --- a/client/app/scripts/stores/app-store.js +++ b/client/app/scripts/stores/app-store.js @@ -537,7 +537,7 @@ export class AppStore extends Store { if (mouseOverNodeId === nodeId) { mouseOverNodeId = null; } - if (nodes.has(nodeId) && _.contains(mouseOverEdgeId, nodeId)) { + if (nodes.has(nodeId) && _.includes(mouseOverEdgeId, nodeId)) { mouseOverEdgeId = null; } nodes = nodes.delete(nodeId); diff --git a/client/package.json b/client/package.json index 9e2e2bf662..bc69d7cc0c 100644 --- a/client/package.json +++ b/client/package.json @@ -10,21 +10,21 @@ "d3": "~3.5.5", "dagre": "0.7.4", "debug": "~2.2.0", - "filesize": "3.1.4", + "filesize": "3.2.1", "flux": "2.1.1", - "font-awesome": "4.4.0", + "font-awesome": "4.5.0", "font-awesome-webpack": "0.0.4", "immutable": "~3.7.4", - "lodash": "~3.10.1", - "materialize-css": "0.97.2", + "lodash": "~4.6.1", + "materialize-css": "0.97.5", "moment": "2.12.0", - "page": "1.6.4", - "react": "0.14.3", - "react-addons-pure-render-mixin": "0.14.3", - "react-addons-transition-group": "0.14.3", - "react-addons-update": "0.14.3", - "react-dom": "0.14.3", - "react-motion": "0.3.1", + "page": "1.7.0", + "react": "^0.14.7", + "react-addons-pure-render-mixin": "^0.14.7", + "react-addons-transition-group": "^0.14.7", + "react-addons-update": "^0.14.7", + "react-dom": "^0.14.7", + "react-motion": "0.4.2", "reqwest": "~2.0.5", "timely": "0.1.0" }, From e4270f69b70d178274c6d6d1d0aef10dccd0177d Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 15 Mar 2016 18:09:45 +0100 Subject: [PATCH 2/2] Upgraded dev deps and linted JS according to latest airbnb rules --- client/.eslintrc | 3 +- client/app/scripts/actions/app-actions.js | 52 +- client/app/scripts/charts/edge.js | 21 +- client/app/scripts/charts/node-shape-cloud.js | 20 +- .../app/scripts/charts/node-shape-heptagon.js | 11 +- client/app/scripts/charts/node-shape-hex.js | 10 +- .../app/scripts/charts/node-shape-square.js | 16 +- client/app/scripts/charts/node.js | 25 +- client/app/scripts/charts/nodes-chart.js | 162 +++-- client/app/scripts/charts/nodes-error.js | 28 +- client/app/scripts/charts/nodes-layout.js | 45 +- .../components/__tests__/node-details-test.js | 3 +- client/app/scripts/components/app.js | 3 +- .../app/scripts/components/debug-toolbar.js | 49 +- client/app/scripts/components/details.js | 25 +- .../scripts/components/embedded-terminal.js | 3 +- client/app/scripts/components/footer.js | 16 +- client/app/scripts/components/logo.js | 111 ++-- client/app/scripts/components/node-details.js | 46 +- .../node-details/node-details-controls.js | 46 +- .../node-details/node-details-health-item.js | 22 +- .../node-details-health-overflow-item.js | 16 +- .../node-details/node-details-health.js | 8 +- .../node-details/node-details-info.js | 21 +- .../node-details/node-details-labels.js | 33 +- .../node-details/node-details-relatives.js | 12 +- .../node-details-table-node-metric.js | 14 +- .../node-details/node-details-table.js | 41 +- client/app/scripts/components/show-more.js | 4 +- client/app/scripts/components/sidebar.js | 14 +- client/app/scripts/components/sparkline.js | 21 +- client/app/scripts/components/status.js | 62 +- client/app/scripts/components/terminal.js | 11 +- client/app/scripts/components/topologies.js | 13 +- .../scripts/components/topology-options.js | 8 +- .../app/scripts/dispatcher/app-dispatcher.js | 8 +- client/app/scripts/hoc/metric-feeder.js | 3 +- .../stores/__tests__/app-store-test.js | 57 +- client/app/scripts/stores/app-store.js | 621 +++++++++--------- client/app/scripts/terminal-main.js | 2 +- .../utils/__tests__/string-utils-test.js | 6 +- .../utils/__tests__/web-api-utils-test.js | 12 +- client/app/scripts/utils/color-utils.js | 3 +- client/app/scripts/utils/file-utils.js | 22 +- client/app/scripts/utils/router-utils.js | 8 +- client/app/scripts/utils/topology-utils.js | 18 +- .../app/scripts/utils/update-buffer-utils.js | 20 +- client/app/scripts/utils/web-api-utils.js | 75 +-- client/package.json | 40 +- 49 files changed, 919 insertions(+), 971 deletions(-) diff --git a/client/.eslintrc b/client/.eslintrc index b620f294f3..643e0f06c9 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -7,7 +7,8 @@ }, "rules": { "comma-dangle": 0, - "func-names": 0, + "object-curly-spacing": 0, + "react/jsx-closing-bracket-location": 0, "react/sort-comp": 0, "react/prop-types": 0 } diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index c0cbbfa981..1464224048 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -15,9 +15,9 @@ const log = debug('scope:app-actions'); export function changeTopologyOption(option, value, topologyId) { AppDispatcher.dispatch({ type: ActionTypes.CHANGE_TOPOLOGY_OPTION, - topologyId: topologyId, - option: option, - value: value + topologyId, + option, + value }); updateRoute(); // update all request workers with new options @@ -53,7 +53,7 @@ export function clickCloseDetails(nodeId) { export function clickCloseTerminal(pipeId, closePipe) { AppDispatcher.dispatch({ type: ActionTypes.CLICK_CLOSE_TERMINAL, - pipeId: pipeId + pipeId }); if (closePipe) { deletePipe(pipeId); @@ -130,7 +130,7 @@ export function clickShowTopologyForNode(topologyId, nodeId) { export function clickTopology(topologyId) { AppDispatcher.dispatch({ type: ActionTypes.CLICK_TOPOLOGY, - topologyId: topologyId + topologyId }); updateRoute(); resetUpdateBuffer(); @@ -149,7 +149,7 @@ export function openWebsocket() { export function clearControlError(nodeId) { AppDispatcher.dispatch({ type: ActionTypes.CLEAR_CONTROL_ERROR, - nodeId: nodeId + nodeId }); } @@ -162,7 +162,7 @@ export function closeWebsocket() { export function doControl(nodeId, control) { AppDispatcher.dispatch({ type: ActionTypes.DO_CONTROL, - nodeId: nodeId + nodeId }); doControlRequest(nodeId, control); } @@ -170,14 +170,14 @@ export function doControl(nodeId, control) { export function enterEdge(edgeId) { AppDispatcher.dispatch({ type: ActionTypes.ENTER_EDGE, - edgeId: edgeId + edgeId }); } export function enterNode(nodeId) { AppDispatcher.dispatch({ type: ActionTypes.ENTER_NODE, - nodeId: nodeId + nodeId }); } @@ -191,7 +191,7 @@ export function hitEsc() { updateRoute(); // Dont deselect node on ESC if there is a controlPipe (keep terminal open) } else if (AppStore.getTopCardNodeId() && !controlPipe) { - AppDispatcher.dispatch({type: ActionTypes.DESELECT_NODE}); + AppDispatcher.dispatch({ type: ActionTypes.DESELECT_NODE }); updateRoute(); } } @@ -199,21 +199,21 @@ export function hitEsc() { export function leaveEdge(edgeId) { AppDispatcher.dispatch({ type: ActionTypes.LEAVE_EDGE, - edgeId: edgeId + edgeId }); } export function leaveNode(nodeId) { AppDispatcher.dispatch({ type: ActionTypes.LEAVE_NODE, - nodeId: nodeId + nodeId }); } export function receiveControlError(nodeId, err) { AppDispatcher.dispatch({ type: ActionTypes.DO_CONTROL_ERROR, - nodeId: nodeId, + nodeId, error: err }); } @@ -221,14 +221,14 @@ export function receiveControlError(nodeId, err) { export function receiveControlSuccess(nodeId) { AppDispatcher.dispatch({ type: ActionTypes.DO_CONTROL_SUCCESS, - nodeId: nodeId + nodeId }); } export function receiveNodeDetails(details) { AppDispatcher.dispatch({ type: ActionTypes.RECEIVE_NODE_DETAILS, - details: details + details }); } @@ -238,7 +238,7 @@ export function receiveNodesDelta(delta) { } else { AppDispatcher.dispatch({ type: ActionTypes.RECEIVE_NODES_DELTA, - delta: delta + delta }); } } @@ -246,7 +246,7 @@ export function receiveNodesDelta(delta) { export function receiveTopologies(topologies) { AppDispatcher.dispatch({ type: ActionTypes.RECEIVE_TOPOLOGIES, - topologies: topologies + topologies }); getNodesDelta( AppStore.getCurrentTopologyUrl(), @@ -270,8 +270,8 @@ export function receiveControlPipeFromParams(pipeId, rawTty) { // TODO add nodeId AppDispatcher.dispatch({ type: ActionTypes.RECEIVE_CONTROL_PIPE, - pipeId: pipeId, - rawTty: rawTty + pipeId, + rawTty }); } @@ -289,9 +289,9 @@ export function receiveControlPipe(pipeId, nodeId, rawTty) { AppDispatcher.dispatch({ type: ActionTypes.RECEIVE_CONTROL_PIPE, - nodeId: nodeId, - pipeId: pipeId, - rawTty: rawTty + nodeId, + pipeId, + rawTty }); updateRoute(); @@ -300,14 +300,14 @@ export function receiveControlPipe(pipeId, nodeId, rawTty) { export function receiveControlPipeStatus(pipeId, status) { AppDispatcher.dispatch({ type: ActionTypes.RECEIVE_CONTROL_PIPE_STATUS, - pipeId: pipeId, - status: status + pipeId, + status }); } export function receiveError(errorUrl) { AppDispatcher.dispatch({ - errorUrl: errorUrl, + errorUrl, type: ActionTypes.RECEIVE_ERROR }); } @@ -321,7 +321,7 @@ export function receiveNotFound(nodeId) { export function route(state) { AppDispatcher.dispatch({ - state: state, + state, type: ActionTypes.ROUTE_TOPOLOGY }); getTopologies( diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js index a2f51e8950..2ff2b1438d 100644 --- a/client/app/scripts/charts/edge.js +++ b/client/app/scripts/charts/edge.js @@ -7,23 +7,23 @@ import { enterEdge, leaveEdge } from '../actions/app-actions'; const line = d3.svg.line() .interpolate('basis') - .x(function(d) { return d.x; }) - .y(function(d) { return d.y; }); + .x(d => d.x) + .y(d => d.y); const animConfig = {stiffness: 80, damping: 20}; -const flattenPoints = function(points) { +const flattenPoints = points => { const flattened = {}; - points.forEach(function(point, i) { - flattened['x' + i] = spring(point.x, animConfig); - flattened['y' + i] = spring(point.y, animConfig); + points.forEach((point, i) => { + flattened[`x${i}`] = spring(point.x, animConfig); + flattened[`y${i}`] = spring(point.y, animConfig); }); return flattened; }; -const extractPoints = function(points) { +const extractPoints = points => { const extracted = []; - _.each(points, function(value, key) { + _.each(points, (value, key) => { const axis = key[0]; const index = key.slice(1); if (!extracted[index]) { @@ -70,10 +70,11 @@ export default class Edge extends React.Component { return ( - {function(interpolated) { + {(interpolated) => { const path = line(extractPoints(interpolated)); return ( - + diff --git a/client/app/scripts/charts/node-shape-cloud.js b/client/app/scripts/charts/node-shape-cloud.js index 2d98989567..f77af9346b 100644 --- a/client/app/scripts/charts/node-shape-cloud.js +++ b/client/app/scripts/charts/node-shape-cloud.js @@ -3,7 +3,11 @@ import d3 from 'd3'; import { isContrastMode } from '../utils/contrast-utils'; -const CLOUD_PATH = 'M 1920,384 Q 1920,225 1807.5,112.5 1695,0 1536,0 H 448 Q 263,0 131.5,131.5 0,263 0,448 0,580 71,689.5 142,799 258,853 q -2,28 -2,43 0,212 150,362 150,150 362,150 158,0 286.5,-88 128.5,-88 187.5,-230 70,62 166,62 106,0 181,-75 75,-75 75,-181 0,-75 -41,-138 129,-30 213,-134.5 84,-104.5 84,-239.5 z'; +const CLOUD_PATH = 'M 1920,384 Q 1920,225 1807.5,112.5 1695,0 1536,0 H 448 ' + + 'Q 263,0 131.5,131.5 0,263 0,448 0,580 71,689.5 142,799 258,853 ' + + 'q -2,28 -2,43 0,212 150,362 150,150 362,150 158,0 286.5,-88 128.5,-88 ' + + '187.5,-230 70,62 166,62 106,0 181,-75 75,-75 75,-181 0,-75 -41,-138 ' + + '129,-30 213,-134.5 84,-104.5 84,-239.5 z'; function toPoint(stringPair) { return stringPair.split(',').map(p => parseFloat(p, 10)); @@ -24,14 +28,12 @@ export default function NodeShapeCloud({highlighted, size, color}) { const baseScale = (size * 2) / pathSize; const strokeWidth = isContrastMode() ? 6 / baseScale : 4 / baseScale; - const pathProps = (v) => { - return { - d: CLOUD_PATH, - fill: 'none', - transform: `scale(-${v * baseScale}) translate(-${cx},-${cy})`, - strokeWidth - }; - }; + const pathProps = v => ({ + d: CLOUD_PATH, + fill: 'none', + transform: `scale(-${v * baseScale}) translate(-${cx},-${cy})`, + strokeWidth + }); return ( diff --git a/client/app/scripts/charts/node-shape-heptagon.js b/client/app/scripts/charts/node-shape-heptagon.js index f595d86595..98652e5f1c 100644 --- a/client/app/scripts/charts/node-shape-heptagon.js +++ b/client/app/scripts/charts/node-shape-heptagon.js @@ -16,12 +16,10 @@ function polygon(r, sides) { export default function NodeShapeHeptagon({onlyHighlight, highlighted, size, color}) { const scaledSize = size * 1.0; - const pathProps = (v) => { - return { - d: line(polygon(scaledSize * v, 7)), - transform: `rotate(90)` - }; - }; + const pathProps = v => ({ + d: line(polygon(scaledSize * v, 7)), + transform: 'rotate(90)' + }); const hightlightNode = ; @@ -42,4 +40,3 @@ export default function NodeShapeHeptagon({onlyHighlight, highlighted, size, col ); } - diff --git a/client/app/scripts/charts/node-shape-hex.js b/client/app/scripts/charts/node-shape-hex.js index 71b351b109..42236d8b55 100644 --- a/client/app/scripts/charts/node-shape-hex.js +++ b/client/app/scripts/charts/node-shape-hex.js @@ -25,12 +25,10 @@ function getPoints(h) { export default function NodeShapeHex({onlyHighlight, highlighted, size, color}) { - const pathProps = (v) => { - return { - d: getPoints(size * v * 2), - transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})` - }; - }; + const pathProps = v => ({ + d: getPoints(size * v * 2), + transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})` + }); const hightlightNode = ; diff --git a/client/app/scripts/charts/node-shape-square.js b/client/app/scripts/charts/node-shape-square.js index ea1c1507e8..5308a72a5f 100644 --- a/client/app/scripts/charts/node-shape-square.js +++ b/client/app/scripts/charts/node-shape-square.js @@ -1,15 +1,13 @@ import React from 'react'; export default function NodeShapeSquare({onlyHighlight, highlighted, size, color, rx = 0, ry = 0}) { - const rectProps = (v) => { - return { - width: v * size * 2, - height: v * size * 2, - rx: v * size * rx, - ry: v * size * ry, - transform: `translate(-${size * v}, -${size * v})` - }; - }; + const rectProps = v => ({ + width: v * size * 2, + height: v * size * 2, + rx: v * size * rx, + ry: v * size * ry, + transform: `translate(-${size * v}, -${size * v})` + }); const hightlightNode = ; diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index 43be883130..5ad277cffb 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -14,18 +14,15 @@ import NodeShapeCloud from './node-shape-cloud'; function stackedShape(Shape) { const factory = React.createFactory(NodeShapeStack); - - return function(props) { - return factory(Object.assign({}, props, {shape: Shape})); - }; + return props => factory(Object.assign({}, props, {shape: Shape})); } const nodeShapes = { - 'circle': NodeShapeCircle, - 'hexagon': NodeShapeHex, - 'heptagon': NodeShapeHeptagon, - 'square': NodeShapeRoundedSquare, - 'cloud': NodeShapeCloud + circle: NodeShapeCircle, + hexagon: NodeShapeHex, + heptagon: NodeShapeHeptagon, + square: NodeShapeRoundedSquare, + cloud: NodeShapeCloud }; function getNodeShape({shape, stack}) { @@ -98,7 +95,7 @@ export default class Node extends React.Component { labelOffsetY: spring(labelOffsetY, animConfig), subLabelOffsetY: spring(subLabelOffsetY, animConfig) }}> - {function(interpolated) { + {(interpolated) => { const transform = `translate(${interpolated.x},${interpolated.y})`; return ( - {label} - {subLabel} @@ -127,7 +126,7 @@ export default class Node extends React.Component { const allowedChars = maxWidth / averageCharLength; let truncatedText = text; if (text && text.length > allowedChars) { - truncatedText = text.slice(0, allowedChars) + '...'; + truncatedText = `${text.slice(0, allowedChars)}...`; } return truncatedText; } diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index 4fc139a0fb..0b04767cd5 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -100,7 +100,8 @@ export default class NodesChart extends React.Component { } renderGraphNodes(nodes, nodeScale) { - const hasSelectedNode = this.props.selectedNodeId && this.props.nodes.has(this.props.selectedNodeId); + const hasSelectedNode = this.props.selectedNodeId + && this.props.nodes.has(this.props.selectedNodeId); const adjacency = hasSelectedNode ? AppStore.getAdjacentNodes(this.props.selectedNodeId) : null; const onNodeClick = this.props.onNodeClick; const zoomScale = this.state.scale; @@ -117,9 +118,7 @@ export default class NodesChart extends React.Component { && (this.props.selectedNodeId === node.get('id') || adjacency.includes(node.get('id'))); return node.set('focused', focused); }; - const setBlurred = node => { - return node.set('blurred', hasSelectedNode && !node.get('focused')); - }; + const setBlurred = node => node.set('blurred', hasSelectedNode && !node.get('focused')); // make sure blurred nodes are in the background const sortNodes = node => { @@ -138,55 +137,49 @@ export default class NodesChart extends React.Component { .map(setFocused) .map(setBlurred) .sortBy(sortNodes) - .map(node => { - return ( - ); - }); + .map(node => ); } renderGraphEdges(edges) { const selectedNodeId = this.props.selectedNodeId; const hasSelectedNode = selectedNodeId && this.props.nodes.has(selectedNodeId); - const setHighlighted = edge => { - return edge.set('highlighted', _.includes(this.props.highlightedEdgeIds, edge.get('id'))); - }; - const setBlurred = edge => { - return (edge.set('blurred', hasSelectedNode - && edge.get('source') !== selectedNodeId - && edge.get('target') !== selectedNodeId)); - }; + const setHighlighted = edge => edge.set('highlighted', _.includes(this.props.highlightedEdgeIds, + edge.get('id'))); + + const setBlurred = edge => edge.set('blurred', hasSelectedNode + && edge.get('source') !== selectedNodeId + && edge.get('target') !== selectedNodeId); return edges .toIndexedSeq() .map(setHighlighted) .map(setBlurred) - .map(edge => { - return ( - - ); - }); + .map(edge => + ); } renderMaxNodesError(show) { @@ -203,9 +196,12 @@ export default class NodesChart extends React.Component { ); @@ -217,7 +213,7 @@ export default class NodesChart extends React.Component { const scale = this.state.scale; const translate = this.state.panTranslate; - const transform = 'translate(' + translate + ') scale(' + scale + ')'; + const transform = `translate(${translate}) scale(${scale})`; const svgClassNames = this.state.maxNodesExceeded || nodeElements.size === 0 ? 'hide' : ''; const errorEmpty = this.renderEmptyTopologyError(AppStore.isTopologyEmpty()); const errorMaxNodesExceeded = this.renderMaxNodesError(this.state.maxNodesExceeded); @@ -226,9 +222,10 @@ export default class NodesChart extends React.Component {
{errorEmpty} {errorMaxNodesExceeded} - + - + @@ -244,30 +241,28 @@ export default class NodesChart extends React.Component { } initNodes(topology) { - return topology.map((node, id) => { - // copy relevant fields to state nodes - return makeMap({ - id: id, - label: node.get('label_major'), - pseudo: node.get('pseudo'), - subLabel: node.get('label_minor'), - nodeCount: node.get('node_count'), - rank: node.get('rank'), - shape: node.get('shape'), - stack: node.get('stack'), - x: 0, - y: 0 - }); - }); + // copy relevant fields to state nodes + return topology.map((node, id) => makeMap({ + id, + label: node.get('label_major'), + pseudo: node.get('pseudo'), + subLabel: node.get('label_minor'), + nodeCount: node.get('node_count'), + rank: node.get('rank'), + shape: node.get('shape'), + stack: node.get('stack'), + x: 0, + y: 0 + })); } initEdges(topology, stateNodes) { let edges = makeMap(); - topology.forEach(function(node, nodeId) { + topology.forEach((node, nodeId) => { const adjacency = node.get('adjacency'); if (adjacency) { - adjacency.forEach(function(adjacent) { + adjacency.forEach(adjacent => { const edge = [nodeId, adjacent]; const edgeId = edge.join(EDGE_ID_SEPARATOR); @@ -282,8 +277,8 @@ export default class NodesChart extends React.Component { edges = edges.set(edgeId, makeMap({ id: edgeId, value: 1, - source: source, - target: target + source, + target })); } }); @@ -305,7 +300,7 @@ export default class NodesChart extends React.Component { const adjacency = AppStore.getAdjacentNodes(props.selectedNodeId); const adjacentLayoutNodeIds = []; - adjacency.forEach(function(adjacentId) { + adjacency.forEach(adjacentId => { // filter loopback if (adjacentId !== props.selectedNodeId) { adjacentLayoutNodeIds.push(adjacentId); @@ -315,7 +310,8 @@ export default class NodesChart extends React.Component { // move origin node to center of viewport const zoomScale = state.scale; const translate = state.panTranslate; - const centerX = (-translate[0] + (props.width + MARGINS.left - DETAILS_PANEL_WIDTH) / 2) / zoomScale; + const centerX = (-translate[0] + (props.width + MARGINS.left + - DETAILS_PANEL_WIDTH) / 2) / zoomScale; const centerY = (-translate[1] + (props.height + MARGINS.top) / 2) / zoomScale; stateNodes = stateNodes.mergeIn([props.selectedNodeId], { x: centerX, @@ -379,12 +375,10 @@ export default class NodesChart extends React.Component { this.zoom.scale(state.scale); this.zoom.translate(state.panTranslate); - const nodes = state.nodes.map(node => { - return node.merge({ - x: node.get('px'), - y: node.get('py') - }); - }); + const nodes = state.nodes.map(node => node.merge({ + x: node.get('px'), + y: node.get('py') + })); const edges = state.edges.map(edge => { if (edge.has('ppoints')) { @@ -422,7 +416,7 @@ export default class NodesChart extends React.Component { const timedLayouter = timely(doLayout); const graph = timedLayouter(stateNodes, stateEdges, options); - log('graph layout took ' + timedLayouter.time + 'ms'); + log(`graph layout took ${timedLayouter.time}ms`); // layout was aborted if (!graph) { @@ -432,15 +426,11 @@ export default class NodesChart extends React.Component { stateEdges = graph.edges; // save coordinates for restore - stateNodes = stateNodes.map(node => { - return node.merge({ - px: node.get('x'), - py: node.get('y') - }); - }); - stateEdges = stateEdges.map(edge => { - return edge.set('ppoints', edge.get('points')); - }); + stateNodes = stateNodes.map(node => node.merge({ + px: node.get('x'), + py: node.get('y') + })); + stateEdges = stateEdges.map(edge => edge.set('ppoints', edge.get('points'))); // adjust layout based on viewport const xFactor = (props.width - MARGINS.left - MARGINS.right) / graph.width; @@ -458,7 +448,7 @@ export default class NodesChart extends React.Component { nodes: stateNodes, edges: stateEdges, scale: zoomScale, - nodeScale: nodeScale, + nodeScale, maxNodesExceeded: false }; } diff --git a/client/app/scripts/charts/nodes-error.js b/client/app/scripts/charts/nodes-error.js index 68bc4af890..877fa25173 100644 --- a/client/app/scripts/charts/nodes-error.js +++ b/client/app/scripts/charts/nodes-error.js @@ -1,20 +1,18 @@ import React from 'react'; -export default class NodesError extends React.Component { - render() { - let classNames = 'nodes-chart-error'; - if (this.props.hidden) { - classNames += ' hide'; - } - const iconClassName = 'fa ' + this.props.faIconClass; +export default function NodesError({children, faIconClass, hidden}) { + let classNames = 'nodes-chart-error'; + if (hidden) { + classNames += ' hide'; + } + const iconClassName = `fa ${faIconClass}`; - return ( -
-
- -
- {this.props.children} + return ( +
+
+
- ); - } + {children} +
+ ); } diff --git a/client/app/scripts/charts/nodes-layout.js b/client/app/scripts/charts/nodes-layout.js index c6543a8a3d..97b13fe4e8 100644 --- a/client/app/scripts/charts/nodes-layout.js +++ b/client/app/scripts/charts/nodes-layout.js @@ -41,7 +41,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) { let edges = imEdges; if (nodes.size > MAX_NODES) { - log('Too many nodes for graph layout engine. Limit: ' + MAX_NODES); + log(`Too many nodes for graph layout engine. Limit: ${MAX_NODES}`); return null; } @@ -54,8 +54,8 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) { // configure node margins graph.setGraph({ - nodesep: nodesep, - ranksep: ranksep + nodesep, + ranksep }); // add nodes to the graph if not already there @@ -140,6 +140,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) { * @return {Object} modified layout */ function layoutSingleNodes(layout, opts) { + const result = Object.assign({}, layout); const options = opts || {}; const margins = options.margins || DEFAULT_MARGINS; const scale = options.scale || DEFAULT_SCALE; @@ -203,12 +204,12 @@ function layoutSingleNodes(layout, opts) { }); // adjust layout dimensions if graph is now bigger - layout.width = Math.max(layout.width, singleX + nodeWidth / 2 + nodesep); - layout.height = Math.max(layout.height, singleY + nodeHeight / 2 + ranksep); - layout.nodes = nodes; + result.width = Math.max(layout.width, singleX + nodeWidth / 2 + nodesep); + result.height = Math.max(layout.height, singleY + nodeHeight / 2 + ranksep); + result.nodes = nodes; } - return layout; + return result; } /** @@ -218,6 +219,7 @@ function layoutSingleNodes(layout, opts) { * @return {Object} modified layout */ function shiftLayoutToCenter(layout, opts) { + const result = Object.assign({}, layout); const options = opts || {}; const margins = options.margins || DEFAULT_MARGINS; const width = options.width || DEFAULT_WIDTH; @@ -233,14 +235,12 @@ function shiftLayoutToCenter(layout, opts) { offsetY = (height - layout.height) / 2 + margins.top; } - layout.nodes = layout.nodes.map(node => { - return node.merge({ - x: node.get('x') + offsetX, - y: node.get('y') + offsetY - }); - }); + result.nodes = layout.nodes.map(node => node.merge({ + x: node.get('x') + offsetX, + y: node.get('y') + offsetY + })); - layout.edges = layout.edges.map(edge => { + result.edges = layout.edges.map(edge => { const points = edge.get('points').map(point => ({ x: point.x + offsetX, y: point.y + offsetY @@ -248,7 +248,7 @@ function shiftLayoutToCenter(layout, opts) { return edge.set('points', points); }); - return layout; + return result; } /** @@ -320,16 +320,16 @@ function cloneLayout(layout, nodes, edges) { * @return {Object} modified layout */ function copyLayoutProperties(layout, nodeCache, edgeCache) { - layout.nodes = layout.nodes.map(node => { - return node.merge(nodeCache.get(node.get('id'))); - }); - layout.edges = layout.edges.map(edge => { - if (edgeCache.has(edge.get('id')) && hasSameEndpoints(edgeCache.get(edge.get('id')), layout.nodes)) { + const result = Object.assign({}, layout); + result.nodes = layout.nodes.map(node => node.merge(nodeCache.get(node.get('id')))); + result.edges = layout.edges.map(edge => { + if (edgeCache.has(edge.get('id')) + && hasSameEndpoints(edgeCache.get(edge.get('id')), result.nodes)) { return edge.merge(edgeCache.get(edge.get('id'))); } return setSimpleEdgePoints(edge, nodeCache); }); - return layout; + return result; } /** @@ -361,7 +361,8 @@ export function doLayout(immNodes, immEdges, opts) { let layout; ++layoutRuns; - if (!options.forceRelayout && cachedLayout && nodeCache && edgeCache && !hasUnseenNodes(immNodes, nodeCache)) { + if (!options.forceRelayout && cachedLayout && nodeCache && edgeCache + && !hasUnseenNodes(immNodes, nodeCache)) { log('skip layout, trivial adjustment', ++layoutRunsTrivial, layoutRuns); layout = cloneLayout(cachedLayout, immNodes, immEdges); // copy old properties, works also if nodes get re-added diff --git a/client/app/scripts/components/__tests__/node-details-test.js b/client/app/scripts/components/__tests__/node-details-test.js index 243368d5ac..9a21ecadb6 100644 --- a/client/app/scripts/components/__tests__/node-details-test.js +++ b/client/app/scripts/components/__tests__/node-details-test.js @@ -28,7 +28,8 @@ describe('NodeDetails', () => { it('shows n/a when node was not found', () => { const c = TestUtils.renderIntoDocument(); - const notFound = TestUtils.findRenderedDOMComponentWithClass(c, 'node-details-header-notavailable'); + const notFound = TestUtils.findRenderedDOMComponentWithClass(c, + 'node-details-header-notavailable'); expect(notFound).toBeDefined(); }); diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index c5bace2eef..cf54673a61 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -99,7 +99,8 @@ export default class App extends React.Component {
- +
_.sample(collection)); -}; +const sample = (collection) => _.range(_.random(4)).map(() => _.sample(collection)); -const deltaAdd = function(name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) { - return { - 'adjacency': adjacency, - 'controls': {}, - 'shape': shape, - 'stack': stack, - 'node_count': nodeCount, - 'id': name, - 'label_major': name, - 'label_minor': 'weave-1', - 'latest': {}, - 'metadata': {}, - 'origins': [], - 'rank': 'alpine' - }; -}; +const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) => ({ + adjacency, + controls: {}, + shape, + stack, + node_count: nodeCount, + id: name, + label_major: name, + label_minor: 'weave-1', + latest: {}, + metadata: {}, + origins: [], + rank: 'alpine' +}); function addAllVariants() { - const newNodes = _.flattenDeep(SHAPES.map(s => { - return STACK_VARIANTS.map(stack => { - if (!stack) return [deltaAdd([s, 1, stack].join('-'), [], s, stack, 1)]; - return NODE_COUNTS.map(n => { - return deltaAdd([s, n, stack].join('-'), [], s, stack, n); - }); - }); - })); + const newNodes = _.flattenDeep(SHAPES.map(s => STACK_VARIANTS.map(stack => { + if (!stack) return [deltaAdd([s, 1, stack].join('-'), [], s, stack, 1)]; + return NODE_COUNTS.map(n => deltaAdd([s, n, stack].join('-'), [], s, stack, n)); + }))); receiveNodesDelta({ add: newNodes @@ -50,7 +43,7 @@ function addAllVariants() { function addNodes(n) { const ns = AppStore.getNodes(); const nodeNames = ns.keySeq().toJS(); - const newNodeNames = _.range(ns.size, ns.size + n).map((i) => 'zing' + i); + const newNodeNames = _.range(ns.size, ns.size + n).map((i) => `zing${i}`); const allNodes = _(nodeNames).concat(newNodeNames).value(); receiveNodesDelta({ diff --git a/client/app/scripts/components/details.js b/client/app/scripts/components/details.js index 540b81dd92..17aa4a09a3 100644 --- a/client/app/scripts/components/details.js +++ b/client/app/scripts/components/details.js @@ -2,21 +2,14 @@ import React from 'react'; import DetailsCard from './details-card'; -export default class Details extends React.Component { - +export default function Details({controlStatus, details, nodes}) { // render all details as cards, later cards go on top - render() { - const details = this.props.details.toIndexedSeq(); - return ( -
- {details.map((obj, index) => { - return ( - - ); - })} -
- ); - } + return ( +
+ {details.toIndexedSeq().map((obj, index) => + )} +
+ ); } diff --git a/client/app/scripts/components/embedded-terminal.js b/client/app/scripts/components/embedded-terminal.js index e56094ba04..291820fb00 100644 --- a/client/app/scripts/components/embedded-terminal.js +++ b/client/app/scripts/components/embedded-terminal.js @@ -2,7 +2,8 @@ import React from 'react'; import { getNodeColor, getNodeColorDark } from '../utils/color-utils'; import Terminal from './terminal'; -import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS, DETAILS_PANEL_OFFSET } from '../constants/styles'; +import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS, + DETAILS_PANEL_OFFSET } from '../constants/styles'; export default function EmeddedTerminal({pipe, nodeId, details}) { const node = details.get(nodeId); diff --git a/client/app/scripts/components/footer.js b/client/app/scripts/components/footer.js index 2558713075..590c7db018 100644 --- a/client/app/scripts/components/footer.js +++ b/client/app/scripts/components/footer.js @@ -7,21 +7,25 @@ import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate, clickResumeUpdate } from '../actions/app-actions'; import { basePathSlash } from '../utils/web-api-utils'; -export default (props) => { +export default function Footer(props) { const { hostname, updatePaused, updatePausedAt, version } = props; const contrastMode = isContrastMode(); // link url to switch contrast with current UI state - const otherContrastModeUrl = contrastMode ? basePathSlash(window.location.pathname) : contrastModeUrl; - const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast'; - const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, but may shift nodes around)'; + const otherContrastModeUrl = contrastMode + ? basePathSlash(window.location.pathname) : contrastModeUrl; + const otherContrastModeTitle = contrastMode + ? 'Switch to normal contrast' : 'Switch to high contrast'; + const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, ' + + 'but may shift nodes around)'; // pause button const isPaused = updatePaused; const updateCount = getUpdateBufferSize(); const hasUpdates = updateCount > 0; const pausedAgo = moment(updatePausedAt).fromNow(); - const pauseTitle = isPaused ? `Paused ${pausedAgo}` : 'Pause updates (freezes the nodes in their current layout)'; + const pauseTitle = isPaused + ? `Paused ${pausedAgo}` : 'Pause updates (freezes the nodes in their current layout)'; const pauseAction = isPaused ? clickResumeUpdate : clickPauseUpdate; const pauseClassName = isPaused ? 'footer-icon footer-icon-active' : 'footer-icon'; let pauseLabel = ''; @@ -64,4 +68,4 @@ export default (props) => { ); -}; +} diff --git a/client/app/scripts/components/logo.js b/client/app/scripts/components/logo.js index 90ae97c612..8b402443d6 100644 --- a/client/app/scripts/components/logo.js +++ b/client/app/scripts/components/logo.js @@ -1,59 +1,58 @@ +/* eslint max-len: "off" */ import React from 'react'; -export default class Logo extends React.Component { - render() { - return ( - - - - - - - - - - - - - - - - - - - ); - } +export default function Logo() { + return ( + + + + + + + + + + + + + + + + + + + ); } diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js index 1d8fec325e..a8d93c4e34 100644 --- a/client/app/scripts/components/node-details.js +++ b/client/app/scripts/components/node-details.js @@ -44,7 +44,8 @@ export default class NodeDetails extends React.Component { return (
- {showSwitchTopology && } + {showSwitchTopology && }
@@ -60,7 +61,7 @@ export default class NodeDetails extends React.Component { const tools = this.renderTools(); const styles = { header: { - 'backgroundColor': nodeColor + backgroundColor: nodeColor } }; @@ -103,7 +104,8 @@ export default class NodeDetails extends React.Component {

- {this.props.label} is not visible to Scope when it is not communicating. + {this.props.label} is not visible to Scope when it + is not communicating. Details will become available here when it communicates again.

@@ -113,7 +115,8 @@ export default class NodeDetails extends React.Component { renderTable(table) { const key = _.snakeCase(table.title); - return ; + return (); } render() { @@ -136,10 +139,10 @@ export default class NodeDetails extends React.Component { const tools = this.renderTools(); const styles = { controls: { - 'backgroundColor': brightenColor(nodeColor) + backgroundColor: brightenColor(nodeColor) }, header: { - 'backgroundColor': nodeColor + backgroundColor: nodeColor } }; @@ -174,23 +177,20 @@ export default class NodeDetails extends React.Component { } - {details.connections && details.connections.map(connections => { - return ( -
- -
- ); - })} - - {details.children && details.children.map(children => { - return ( -
- -
- ); - })} - - {details.docker_labels && details.docker_labels.length > 0 &&
+ {details.connections && details.connections.map(connections =>
+ +
+ )} + + {details.children && details.children.map(children =>
+ +
+ )} + + {details.docker_labels && details.docker_labels.length > 0 + &&
Docker Labels
} diff --git a/client/app/scripts/components/node-details/node-details-controls.js b/client/app/scripts/components/node-details/node-details-controls.js index 08e91cf06e..d3028e74d5 100644 --- a/client/app/scripts/components/node-details/node-details-controls.js +++ b/client/app/scripts/components/node-details/node-details-controls.js @@ -2,31 +2,25 @@ import React from 'react'; import NodeDetailsControlButton from './node-details-control-button'; -export default class NodeDetailsControls extends React.Component { - render() { - let spinnerClassName = 'fa fa-circle-o-notch fa-spin'; - if (this.props.pending) { - spinnerClassName += ' node-details-controls-spinner'; - } else { - spinnerClassName += ' node-details-controls-spinner hide'; - } - - return ( -
- {this.props.error &&
- - {this.props.error} -
} - - {this.props.controls && this.props.controls.map(control => { - return ( - - ); - })} - - {this.props.controls && } -
- ); +export default function NodeDetailsControls({controls, error, nodeId, pending}) { + let spinnerClassName = 'fa fa-circle-o-notch fa-spin'; + if (pending) { + spinnerClassName += ' node-details-controls-spinner'; + } else { + spinnerClassName += ' node-details-controls-spinner hide'; } + + return ( +
+ {error &&
+ + {error} +
} + + {controls && controls.map(control => )} + + {controls && } +
+ ); } 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 1c63437ec6..10b66c010f 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 @@ -4,19 +4,17 @@ import Sparkline from '../sparkline'; import metricFeeder from '../../hoc/metric-feeder'; import { formatMetric } from '../../utils/string-utils'; -class NodeDetailsHealthItem extends React.Component { - render() { - return ( -
-
{formatMetric(this.props.value, this.props)}
-
- -
-
{this.props.label}
+function NodeDetailsHealthItem(props) { + return ( +
+
{formatMetric(props.value, props)}
+
+
- ); - } +
{props.label}
+
+ ); } export default metricFeeder(NodeDetailsHealthItem); 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 bb19447656..46fc2ac5f4 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 @@ -3,15 +3,15 @@ import React from 'react'; import metricFeeder from '../../hoc/metric-feeder'; import { formatMetric } from '../../utils/string-utils'; -class NodeDetailsHealthOverflowItem extends React.Component { - render() { - return ( -
-
{formatMetric(this.props.value, this.props)}
-
{this.props.label}
+function NodeDetailsHealthOverflowItem(props) { + return ( +
+
+ {formatMetric(props.value, props)}
- ); - } +
{props.label}
+
+ ); } export default metricFeeder(NodeDetailsHealthOverflowItem); 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 811b195643..18831c2d8b 100644 --- a/client/app/scripts/components/node-details/node-details-health.js +++ b/client/app/scripts/components/node-details/node-details-health.js @@ -32,13 +32,11 @@ export default class NodeDetailsHealth extends React.Component { return (
- {primeMetrics.map(item => { - return ; - })} + {primeMetrics.map(item => )} {showOverflow && this.handleClickMore()} />} + handleClick={this.handleClickMore} />}
- this.handleClickMore()} collection={this.props.metrics} +
); diff --git a/client/app/scripts/components/node-details/node-details-info.js b/client/app/scripts/components/node-details/node-details-info.js index cf85a58d2f..e04062f2f7 100644 --- a/client/app/scripts/components/node-details/node-details-info.js +++ b/client/app/scripts/components/node-details/node-details-info.js @@ -27,19 +27,16 @@ export default class NodeDetailsInfo extends React.Component { } return (
- {rows.map(field => { - return ( -
-
- {field.label} -
-
- {field.value} -
+ {rows.map(field => (
+
+ {field.label}
- ); - })} - this.handleClickMore()} collection={this.props.rows} +
+ {field.value} +
+
+ ))} +
); diff --git a/client/app/scripts/components/node-details/node-details-labels.js b/client/app/scripts/components/node-details/node-details-labels.js index 332641f5dc..300b2a0b2b 100644 --- a/client/app/scripts/components/node-details/node-details-labels.js +++ b/client/app/scripts/components/node-details/node-details-labels.js @@ -1,22 +1,17 @@ import React from 'react'; -export default class NodeDetailsLabels extends React.Component { - render() { - return ( -
- {this.props.rows.map(field => { - return ( -
-
- {field.label} -
-
- {field.value} -
-
- ); - })} -
- ); - } +export default function NodeDetailsLabels({rows}) { + return ( +
+ {rows.map(field => (
+
+ {field.label} +
+
+ {field.value} +
+
+ ))} +
+ ); } diff --git a/client/app/scripts/components/node-details/node-details-relatives.js b/client/app/scripts/components/node-details/node-details-relatives.js index b0813bc27e..864a3b9709 100644 --- a/client/app/scripts/components/node-details/node-details-relatives.js +++ b/client/app/scripts/components/node-details/node-details-relatives.js @@ -16,13 +16,14 @@ export default class NodeDetailsRelatives extends React.Component { handleLimitClick(ev) { ev.preventDefault(); const limit = this.state.limit ? 0 : this.DEFAULT_LIMIT; - this.setState({limit: limit}); + this.setState({limit}); } render() { let relatives = this.props.relatives; const limited = this.state.limit > 0 && relatives.length > this.state.limit; - const showLimitAction = limited || (this.state.limit === 0 && relatives.length > this.DEFAULT_LIMIT); + const showLimitAction = limited || (this.state.limit === 0 + && relatives.length > this.DEFAULT_LIMIT); const limitActionText = limited ? 'Show more' : 'Show less'; if (limited) { relatives = relatives.slice(0, this.state.limit); @@ -30,10 +31,9 @@ export default class NodeDetailsRelatives extends React.Component { return (
- {relatives.map(relative => { - return ; - })} - {showLimitAction && {limitActionText}} + {relatives.map(relative => )} + {showLimitAction && {limitActionText}}
); } diff --git a/client/app/scripts/components/node-details/node-details-table-node-metric.js b/client/app/scripts/components/node-details/node-details-table-node-metric.js index c27c12ab8f..4b1ec52610 100644 --- a/client/app/scripts/components/node-details/node-details-table-node-metric.js +++ b/client/app/scripts/components/node-details/node-details-table-node-metric.js @@ -2,14 +2,12 @@ import React from 'react'; import { formatMetric } from '../../utils/string-utils'; -class NodeDetailsTableNodeMetric extends React.Component { - render() { - return ( - - {formatMetric(this.props.value, this.props)} - - ); - } +function NodeDetailsTableNodeMetric(props) { + return ( + + {formatMetric(props.value, props)} + + ); } export default NodeDetailsTableNodeMetric; diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js index ca259f017c..406651fd0f 100644 --- a/client/app/scripts/components/node-details/node-details-table.js +++ b/client/app/scripts/components/node-details/node-details-table.js @@ -25,7 +25,8 @@ export default class NodeDetailsTable extends React.Component { handleHeaderClick(ev, headerId) { ev.preventDefault(); - const sortedDesc = headerId === this.state.sortBy ? !this.state.sortedDesc : this.state.sortedDesc; + const sortedDesc = headerId === this.state.sortBy + ? !this.state.sortedDesc : this.state.sortedDesc; const sortBy = headerId; this.setState({sortBy, sortedDesc}); } @@ -47,17 +48,15 @@ export default class NodeDetailsTable extends React.Component { getMetaDataSorters() { // returns an array of sorters that will take a node - return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => { - return node => { - const nodeMetadataField = node.metadata[index]; - if (nodeMetadataField) { - if (isNumberField(nodeMetadataField)) { - return parseFloat(nodeMetadataField.value); - } - return nodeMetadataField.value; + return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => node => { + const nodeMetadataField = node.metadata[index]; + if (nodeMetadataField) { + if (isNumberField(nodeMetadataField)) { + return parseFloat(nodeMetadataField.value); } - return null; - }; + return nodeMetadataField.value; + } + return null; }); } @@ -81,8 +80,9 @@ export default class NodeDetailsTable extends React.Component { ['metrics', 'metadata'].forEach(collection => { if (node[collection]) { node[collection].forEach(field => { - field.valueType = collection; - values[field.id] = field; + const result = Object.assign({}, field); + result.valueType = collection; + values[field.id] = result; }); } }); @@ -103,7 +103,8 @@ export default class NodeDetailsTable extends React.Component { this.handleHeaderClick(ev, header.id); }; // sort by first metric by default - const isSorted = this.state.sortBy !== null ? header.id === this.state.sortBy : header.id === defaultSortBy; + const isSorted = this.state.sortBy !== null + ? header.id === this.state.sortBy : header.id === defaultSortBy; const isSortedDesc = isSorted && this.state.sortedDesc; const isSortedAsc = isSorted && !isSortedDesc; if (isSorted) { @@ -111,8 +112,10 @@ export default class NodeDetailsTable extends React.Component { } return ( - {isSortedAsc && } - {isSortedDesc && } + {isSortedAsc + && } + {isSortedDesc + && } {header.label} ); @@ -145,7 +148,8 @@ export default class NodeDetailsTable extends React.Component { render() { const headers = this.renderHeaders(); - let nodes = _.sortBy(this.props.nodes, this.getValueForSortBy, 'label', this.getMetaDataSorters()); + let nodes = _.sortBy(this.props.nodes, this.getValueForSortBy, 'label', + this.getMetaDataSorters()); const limited = nodes && this.state.limit > 0 && nodes.length > this.state.limit; const expanded = this.state.limit === 0; const notShown = nodes.length - this.DEFAULT_LIMIT; @@ -176,7 +180,8 @@ export default class NodeDetailsTable extends React.Component { })} - this.handleLimitClick()} collection={this.props.nodes} expanded={expanded} notShown={notShown} /> +
); } diff --git a/client/app/scripts/components/show-more.js b/client/app/scripts/components/show-more.js index 8da206f33a..a8ed87ec03 100644 --- a/client/app/scripts/components/show-more.js +++ b/client/app/scripts/components/show-more.js @@ -19,11 +19,11 @@ export default class ShowMore extends React.Component { const limitActionIcon = !expanded && notShown > 0 ? 'fa fa-caret-down' : 'fa fa-caret-up'; if (!showLimitAction) { - return ; + return ; } return (
- {limitActionText} + {limitActionText}
); } diff --git a/client/app/scripts/components/sidebar.js b/client/app/scripts/components/sidebar.js index 101f54808b..4a6973c00b 100644 --- a/client/app/scripts/components/sidebar.js +++ b/client/app/scripts/components/sidebar.js @@ -1,11 +1,9 @@ import React from 'react'; -export default class Sidebar extends React.Component { - render() { - return ( -
- {this.props.children} -
- ); - } +export default function Sidebar({children}) { + return ( +
+ {children} +
+ ); } diff --git a/client/app/scripts/components/sparkline.js b/client/app/scripts/components/sparkline.js index 028823e5c8..03c1670049 100644 --- a/client/app/scripts/components/sparkline.js +++ b/client/app/scripts/components/sparkline.js @@ -30,12 +30,10 @@ export default class Sparkline extends React.Component { this.line.interpolate(this.props.interpolate); // Convert dates into D3 dates - data = data.map(d => { - return { - date: parseDate(d.date), - value: d.value - }; - }); + data = data.map(d => ({ + date: parseDate(d.date), + value: d.value + })); // determine date range let firstDate = this.props.first ? parseDate(this.props.first) : data[0].date; @@ -51,16 +49,17 @@ export default class Sparkline extends React.Component { // determine value range const minValue = this.props.min !== undefined ? this.props.min : d3.min(data, d => d.value); - const maxValue = this.props.max !== undefined ? Math.max(this.props.max, d3.max(data, d => d.value)) : d3.max(data, d => d.value); + const maxValue = this.props.max !== undefined + ? Math.max(this.props.max, d3.max(data, d => d.value)) : d3.max(data, d => d.value); this.y.domain([minValue, maxValue]); const lastValue = data[data.length - 1].value; const lastX = this.x(lastDate); const lastY = this.y(lastValue); - const title = 'Last ' + d3.round((lastDate - firstDate) / 1000) + ' seconds, ' + - data.length + ' samples, min: ' + d3.round(d3.min(data, d => d.value), 2) + - ', max: ' + d3.round(d3.max(data, d => d.value), 2) + - ', mean: ' + d3.round(d3.mean(data, d => d.value), 2); + const title = `Last ${d3.round((lastDate - firstDate) / 1000)} seconds, ` + + `${data.length} samples, min: ${d3.round(d3.min(data, d => d.value), 2)}` + + `, max: ${d3.round(d3.max(data, d => d.value), 2)}` + + `, mean: ${d3.round(d3.mean(data, d => d.value), 2)}`; return {title, lastX, lastY, data}; } diff --git a/client/app/scripts/components/status.js b/client/app/scripts/components/status.js index 4b9cb3dcf3..c2a027391a 100644 --- a/client/app/scripts/components/status.js +++ b/client/app/scripts/components/status.js @@ -1,38 +1,36 @@ import React from 'react'; -export default class Status extends React.Component { - render() { - let title = ''; - let text = 'Trying to reconnect...'; - let showWarningIcon = false; - let classNames = 'status sidebar-item'; +export default function Status({errorUrl, topologiesLoaded, topology, websocketClosed}) { + let title = ''; + let text = 'Trying to reconnect...'; + let showWarningIcon = false; + let classNames = 'status sidebar-item'; - if (this.props.errorUrl) { - title = `Cannot reach Scope. Make sure the following URL is reachable: ${this.props.errorUrl}`; - classNames += ' status-loading'; - showWarningIcon = true; - } else if (!this.props.topologiesLoaded) { - text = 'Connecting to Scope...'; - classNames += ' status-loading'; - showWarningIcon = true; - } else if (this.props.websocketClosed) { - classNames += ' status-loading'; - showWarningIcon = true; - } else if (this.props.topology) { - const stats = this.props.topology.get('stats'); - text = `${stats.get('node_count')} nodes`; - if (stats.get('filtered_nodes')) { - text = `${text} (${stats.get('filtered_nodes')} filtered)`; - } - classNames += ' status-stats'; - showWarningIcon = false; + if (errorUrl) { + title = `Cannot reach Scope. Make sure the following URL is reachable: ${errorUrl}`; + classNames += ' status-loading'; + showWarningIcon = true; + } else if (!topologiesLoaded) { + text = 'Connecting to Scope...'; + classNames += ' status-loading'; + showWarningIcon = true; + } else if (websocketClosed) { + classNames += ' status-loading'; + showWarningIcon = true; + } else if (topology) { + const stats = topology.get('stats'); + text = `${stats.get('node_count')} nodes`; + if (stats.get('filtered_nodes')) { + text = `${text} (${stats.get('filtered_nodes')} filtered)`; } - - return ( -
- {showWarningIcon && } - {text} -
- ); + classNames += ' status-stats'; + showWarningIcon = false; } + + return ( +
+ {showWarningIcon && } + {text} +
+ ); } diff --git a/client/app/scripts/components/terminal.js b/client/app/scripts/components/terminal.js index e291ca5ecb..146988fb3b 100644 --- a/client/app/scripts/components/terminal.js +++ b/client/app/scripts/components/terminal.js @@ -1,3 +1,4 @@ +/* eslint no-return-assign: "off", react/jsx-no-bind: "off" */ import debug from 'debug'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -10,7 +11,7 @@ import { getPipeStatus, basePath } from '../utils/web-api-utils'; import Term from '../vendor/term.js'; const wsProto = location.protocol === 'https:' ? 'wss' : 'ws'; -const wsUrl = wsProto + '://' + location.host + basePath(location.pathname); +const wsUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`; const log = debug('scope:terminal'); const DEFAULT_COLS = 80; @@ -66,7 +67,7 @@ function openNewWindow(url, bcr, minWidth = 200) { }; const windowOptionsString = Object.keys(windowOptions) - .map((k) => k + '=' + windowOptions[k]) + .map((k) => `${k}=${windowOptions[k]}`) .join(','); window.open(url, '', windowOptionsString); @@ -92,7 +93,7 @@ export default class Terminal extends React.Component { } createWebsocket(term) { - const socket = new WebSocket(wsUrl + '/api/pipe/' + this.getPipeId()); + const socket = new WebSocket(`${wsUrl}/api/pipe/${this.getPipeId()}`); socket.binaryType = 'arraybuffer'; getPipeStatus(this.getPipeId()); @@ -157,8 +158,8 @@ export default class Terminal extends React.Component { this.resizeTimeout = setTimeout(() => { this.setState({ - pixelPerCol: pixelPerCol, - pixelPerRow: pixelPerRow + pixelPerCol, + pixelPerRow }); this.handleResize(); }, 10); diff --git a/client/app/scripts/components/topologies.js b/client/app/scripts/components/topologies.js index 4e18428ce2..2cc7855588 100644 --- a/client/app/scripts/components/topologies.js +++ b/client/app/scripts/components/topologies.js @@ -19,7 +19,8 @@ export default class Topologies extends React.Component { const isActive = subTopology === this.props.currentTopology; const topologyId = subTopology.get('id'); const title = this.renderTitle(subTopology); - const className = isActive ? 'topologies-sub-item topologies-sub-item-active' : 'topologies-sub-item'; + const className = isActive + ? 'topologies-sub-item topologies-sub-item-active' : 'topologies-sub-item'; return (
- {topology.has('sub_topologies') && topology.get('sub_topologies').map(this.renderSubTopology)} + {topology.has('sub_topologies') + && topology.get('sub_topologies').map(this.renderSubTopology)}
); diff --git a/client/app/scripts/components/topology-options.js b/client/app/scripts/components/topology-options.js index 4c021581b2..af67b61866 100644 --- a/client/app/scripts/components/topology-options.js +++ b/client/app/scripts/components/topology-options.js @@ -27,7 +27,7 @@ export default class TopologyOptions extends React.Component { activeValue = activeOptions.get(option); } else { // get default value - items.forEach(function(item) { + items.forEach(item => { if (item.get('default')) { activeValue = item.get('value'); } @@ -35,7 +35,7 @@ export default class TopologyOptions extends React.Component { } // render active option as text, add other options as actions - items.forEach(function(item) { + items.forEach(item => { if (item.get('value') === activeValue) { activeText = item.get('display'); } else { @@ -62,9 +62,7 @@ export default class TopologyOptions extends React.Component { return (
- {options.toIndexedSeq().map(function(items) { - return this.renderOption(items); - }, this)} + {options.toIndexedSeq().map(items => this.renderOption(items))}
); } diff --git a/client/app/scripts/dispatcher/app-dispatcher.js b/client/app/scripts/dispatcher/app-dispatcher.js index 8869e1c141..918ad5d8ee 100644 --- a/client/app/scripts/dispatcher/app-dispatcher.js +++ b/client/app/scripts/dispatcher/app-dispatcher.js @@ -5,11 +5,9 @@ const log = debug('scope:dispatcher'); const instance = new Dispatcher(); -instance.dispatch = _.wrap(Dispatcher.prototype.dispatch, function(func) { - const args = Array.prototype.slice.call(arguments, 1); - const type = args[0] && args[0].type; - log(type, args[0]); - func.apply(this, args); +instance.dispatch = _.wrap(Dispatcher.prototype.dispatch, (func, payload) => { + log(payload.type, payload); + func.call(instance, payload); }); export default instance; diff --git a/client/app/scripts/hoc/metric-feeder.js b/client/app/scripts/hoc/metric-feeder.js index 306d928993..5fd233687e 100644 --- a/client/app/scripts/hoc/metric-feeder.js +++ b/client/app/scripts/hoc/metric-feeder.js @@ -148,7 +148,8 @@ export default ComposedComponent => class extends React.Component { .filter(dateFilter); const lastValue = samples.length > 0 ? samples[samples.length - 1].value : null; - const slidingWindow = {first: movingFirstDate, last: movingLastDate, max, samples, value: lastValue}; + const slidingWindow = {first: movingFirstDate, + last: movingLastDate, max, samples, value: lastValue}; return ; } diff --git a/client/app/scripts/stores/__tests__/app-store-test.js b/client/app/scripts/stores/__tests__/app-store-test.js index e90356603b..85065a7f80 100644 --- a/client/app/scripts/stores/__tests__/app-store-test.js +++ b/client/app/scripts/stores/__tests__/app-store-test.js @@ -4,9 +4,8 @@ jest.dontMock('../app-store'); // Appstore test suite using Jasmine matchers -describe('AppStore', function() { +describe('AppStore', () => { const ActionTypes = require('../../constants/action-types').default; - let AppDispatcher; let AppStore; let registeredCallback; @@ -150,22 +149,22 @@ describe('AppStore', function() { state: {} }; - beforeEach(function() { + beforeEach(() => { AppStore = require('../app-store').default; - AppDispatcher = AppStore.getDispatcher(); + const AppDispatcher = AppStore.getDispatcher(); const callback = AppDispatcher.dispatch.bind(AppDispatcher); registeredCallback = callback; }); // topology tests - it('init with no topologies', function() { + it('init with no topologies', () => { const topos = AppStore.getTopologies(); expect(topos.size).toBe(0); expect(AppStore.getCurrentTopology()).toBeUndefined(); }); - it('get current topology', function() { + it('get current topology', () => { registeredCallback(ClickTopologyAction); registeredCallback(ReceiveTopologiesAction); @@ -175,7 +174,7 @@ describe('AppStore', function() { expect(AppStore.getCurrentTopologyOptions().get('option1')).toBeDefined(); }); - it('get sub-topology', function() { + it('get sub-topology', () => { registeredCallback(ReceiveTopologiesAction); registeredCallback(ClickSubTopologyAction); @@ -187,7 +186,7 @@ describe('AppStore', function() { // topology options - it('changes topology option', function() { + it('changes topology option', () => { // default options registeredCallback(ReceiveTopologiesAction); registeredCallback(ClickTopologyAction); @@ -211,11 +210,11 @@ describe('AppStore', function() { expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('off'); }); - it('sets topology options from route', function() { + it('sets topology options from route', () => { RouteAction.state = { - 'topologyId': 'topo1', - 'selectedNodeId': null, - 'topologyOptions': {'topo1': {'option1': 'on'}}}; + topologyId: 'topo1', + selectedNodeId: null, + topologyOptions: {topo1: {option1: 'on'}}}; registeredCallback(RouteAction); expect(AppStore.getActiveTopologyOptions().get('option1')).toBe('on'); expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('on'); @@ -227,11 +226,11 @@ describe('AppStore', function() { expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('on'); }); - it('uses default topology options from route', function() { + it('uses default topology options from route', () => { RouteAction.state = { - 'topologyId': 'topo1', - 'selectedNodeId': null, - 'topologyOptions': null}; + topologyId: 'topo1', + selectedNodeId: null, + topologyOptions: null}; registeredCallback(RouteAction); registeredCallback(ReceiveTopologiesAction); registeredCallback(ClickTopologyAction); @@ -241,7 +240,7 @@ describe('AppStore', function() { // nodes delta - it('replaces adjacency on update', function() { + it('replaces adjacency on update', () => { registeredCallback(ReceiveNodesDeltaAction); expect(AppStore.getNodes().toJS().n1.adjacency).toEqual(['n1', 'n2']); registeredCallback(ReceiveNodesDeltaUpdateAction); @@ -250,18 +249,18 @@ describe('AppStore', function() { // browsing - it('shows nodes that were received', function() { + it('shows nodes that were received', () => { registeredCallback(ReceiveNodesDeltaAction); expect(AppStore.getNodes().toJS()).toEqual(NODE_SET); }); - it('knows a route was set', function() { + it('knows a route was set', () => { expect(AppStore.isRouteSet()).toBeFalsy(); registeredCallback(RouteAction); expect(AppStore.isRouteSet()).toBeTruthy(); }); - it('gets selected node after click', function() { + it('gets selected node after click', () => { registeredCallback(ReceiveNodesDeltaAction); registeredCallback(ClickNodeAction); @@ -273,7 +272,7 @@ describe('AppStore', function() { expect(AppStore.getNodes().toJS()).toEqual(NODE_SET); }); - it('keeps showing nodes on navigating back after node click', function() { + it('keeps showing nodes on navigating back after node click', () => { registeredCallback(ReceiveTopologiesAction); registeredCallback(ClickTopologyAction); registeredCallback(ReceiveNodesDeltaAction); @@ -284,13 +283,13 @@ describe('AppStore', function() { expect(AppStore.getAppState().selectedNodeId).toEqual('n1'); // go back in browsing - RouteAction.state = {'topologyId': 'topo1', 'selectedNodeId': null}; + RouteAction.state = {topologyId: 'topo1', selectedNodeId: null}; registeredCallback(RouteAction); expect(AppStore.getSelectedNodeId()).toBe(null); expect(AppStore.getNodes().toJS()).toEqual(NODE_SET); }); - it('closes details when changing topologies', function() { + it('closes details when changing topologies', () => { registeredCallback(ReceiveTopologiesAction); registeredCallback(ClickTopologyAction); registeredCallback(ReceiveNodesDeltaAction); @@ -309,7 +308,7 @@ describe('AppStore', function() { // connection errors - it('resets topology on websocket reconnect', function() { + it('resets topology on websocket reconnect', () => { registeredCallback(ReceiveNodesDeltaAction); expect(AppStore.getNodes().toJS()).toEqual(NODE_SET); @@ -326,7 +325,7 @@ describe('AppStore', function() { // adjacency test - it('returns the correct adjacency set for a node', function() { + it('returns the correct adjacency set for a node', () => { registeredCallback(ReceiveNodesDeltaAction); expect(AppStore.getAdjacentNodes().size).toEqual(0); @@ -341,7 +340,7 @@ describe('AppStore', function() { // empty topology - it('detects that the topology is empty', function() { + it('detects that the topology is empty', () => { registeredCallback(ReceiveTopologiesAction); registeredCallback(ClickTopologyAction); expect(AppStore.isTopologyEmpty()).toBeFalsy(); @@ -355,7 +354,7 @@ describe('AppStore', function() { // selection of relatives - it('keeps relatives as a stack', function() { + it('keeps relatives as a stack', () => { registeredCallback(ClickNodeAction); expect(AppStore.getSelectedNodeId()).toBe('n1'); expect(AppStore.getNodeDetails().size).toEqual(1); @@ -377,7 +376,7 @@ describe('AppStore', function() { expect(AppStore.getNodeDetails().has('rel1')).toBeFalsy(); }); - it('keeps clears stack when sibling is clicked', function() { + it('keeps clears stack when sibling is clicked', () => { registeredCallback(ClickNodeAction); expect(AppStore.getSelectedNodeId()).toBe('n1'); expect(AppStore.getNodeDetails().size).toEqual(1); @@ -400,7 +399,7 @@ describe('AppStore', function() { expect(AppStore.getNodeDetails().has('rel1')).toBeFalsy(); }); - it('selectes relatives topology while keeping node selected', function() { + it('selectes relatives topology while keeping node selected', () => { registeredCallback(ClickTopologyAction); registeredCallback(ReceiveTopologiesAction); expect(AppStore.getCurrentTopology().get('name')).toBe('Topo1'); diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js index 20edade72d..55c6e5441a 100644 --- a/client/app/scripts/stores/app-store.js +++ b/client/app/scripts/stores/app-store.js @@ -62,12 +62,12 @@ const topologySorter = topology => topology.get('rank'); // map for easy lookup function processTopologies(nextTopologies) { // add IDs to topology objects in-place - updateTopologyIds(nextTopologies); + const topologiesWithId = updateTopologyIds(nextTopologies); // cache URLs by ID - topologyUrlsById = setTopologyUrlsById(topologyUrlsById, nextTopologies); + topologyUrlsById = setTopologyUrlsById(topologyUrlsById, topologiesWithId); - const immNextTopologies = fromJS(nextTopologies).sortBy(topologySorter); + const immNextTopologies = fromJS(topologiesWithId).sortBy(topologySorter); topologies = topologies.mergeDeep(immNextTopologies); } @@ -80,7 +80,7 @@ function setDefaultTopologyOptions(topologyList) { topologyList.forEach(topology => { let defaultOptions = makeOrderedMap(); if (topology.has('options')) { - topology.get('options').forEach(function(items, option) { + topology.get('options').forEach((items, option) => { items.forEach(item => { if (item.get('default') === true) { defaultOptions = defaultOptions.set(option, item.get('value')); @@ -106,9 +106,7 @@ function closeNodeDetails(nodeId) { if (nodeDetails.size > 0) { const popNodeId = nodeId || nodeDetails.keySeq().last(); // remove pipe if it belongs to the node being closed - controlPipes = controlPipes.filter(pipe => { - return pipe.get('nodeId') !== popNodeId; - }); + controlPipes = controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId); nodeDetails = nodeDetails.delete(popNodeId); } if (nodeDetails.size === 0 || selectedNodeId === nodeId) { @@ -135,7 +133,7 @@ export class AppStore extends Store { return { controlPipe: this.getControlPipe(), nodeDetails: this.getNodeDetailsState(), - selectedNodeId: selectedNodeId, + selectedNodeId, topologyId: currentTopologyId, topologyOptions: topologyOptions.toJS() // all options }; @@ -152,7 +150,7 @@ export class AppStore extends Store { if (nodes.has(nodeId)) { adjacentNodes = makeSet(nodes.get(nodeId).get('adjacency')); // fill up set with reverse edges - nodes.forEach(function(node, id) { + nodes.forEach((node, id) => { if (node.get('adjacency') && node.get('adjacency').includes(nodeId)) { adjacentNodes = adjacentNodes.add(id); } @@ -200,12 +198,10 @@ export class AppStore extends Store { const adjacency = nodes.get(mouseOverNodeId).get('adjacency'); if (adjacency) { return _.flatten( - adjacency.forEach(function(nodeId) { - return [ - [nodeId, mouseOverNodeId].join(EDGE_ID_SEPARATOR), - [mouseOverNodeId, nodeId].join(EDGE_ID_SEPARATOR) - ]; - }) + adjacency.map((nodeId) => [ + [nodeId, mouseOverNodeId].join(EDGE_ID_SEPARATOR), + [mouseOverNodeId, nodeId].join(EDGE_ID_SEPARATOR) + ]).toJS() ); } } @@ -235,9 +231,9 @@ export class AppStore extends Store { } getNodeDetailsState() { - return nodeDetails.toIndexedSeq().map(details => { - return {id: details.id, label: details.label, topologyId: details.topologyId}; - }).toJS(); + return nodeDetails.toIndexedSeq().map(details => ({ + id: details.id, label: details.label, topologyId: details.topologyId + })).toJS(); } getTopCardNodeId() { @@ -281,7 +277,8 @@ export class AppStore extends Store { } isTopologyEmpty() { - return currentTopology && currentTopology.get('stats') && currentTopology.get('stats').get('node_count') === 0 && nodes.size === 0; + return currentTopology && currentTopology.get('stats') + && currentTopology.get('stats').get('node_count') === 0 && nodes.size === 0; } isUpdatePaused() { @@ -298,327 +295,329 @@ export class AppStore extends Store { } switch (payload.type) { - case ActionTypes.CHANGE_TOPOLOGY_OPTION: - resumeUpdate(); - if (topologyOptions.getIn([payload.topologyId, payload.option]) - !== payload.value) { - nodes = nodes.clear(); - } - topologyOptions = topologyOptions.setIn( - [payload.topologyId, payload.option], - payload.value - ); - this.__emitChange(); - break; - - case ActionTypes.CLEAR_CONTROL_ERROR: - controlStatus = controlStatus.removeIn([payload.nodeId, 'error']); - this.__emitChange(); - break; - - case ActionTypes.CLICK_BACKGROUND: - closeAllNodeDetails(); - this.__emitChange(); - break; - - case ActionTypes.CLICK_CLOSE_DETAILS: - closeNodeDetails(payload.nodeId); - this.__emitChange(); - break; - - case ActionTypes.CLICK_CLOSE_TERMINAL: - controlPipes = controlPipes.clear(); - this.__emitChange(); - break; - - case ActionTypes.CLICK_FORCE_RELAYOUT: - forceRelayout = true; - // fire only once, reset after emitChange - setTimeout(() => { - forceRelayout = false; - }, 0); - this.__emitChange(); - break; - - case ActionTypes.CLICK_NODE: - const prevSelectedNodeId = selectedNodeId; - const prevDetailsStackSize = nodeDetails.size; - // click on sibling closes all - closeAllNodeDetails(); - // select new node if it's not the same (in that case just delesect) - if (prevDetailsStackSize > 1 || prevSelectedNodeId !== payload.nodeId) { - // dont set origin if a node was already selected, suppresses animation - const origin = prevSelectedNodeId === null ? payload.origin : null; - nodeDetails = nodeDetails.set( - payload.nodeId, - { - id: payload.nodeId, - label: payload.label, - origin, - topologyId: currentTopologyId - } + case ActionTypes.CHANGE_TOPOLOGY_OPTION: { + resumeUpdate(); + if (topologyOptions.getIn([payload.topologyId, payload.option]) + !== payload.value) { + nodes = nodes.clear(); + } + topologyOptions = topologyOptions.setIn( + [payload.topologyId, payload.option], + payload.value ); + this.__emitChange(); + break; + } + case ActionTypes.CLEAR_CONTROL_ERROR: { + controlStatus = controlStatus.removeIn([payload.nodeId, 'error']); + this.__emitChange(); + break; + } + case ActionTypes.CLICK_BACKGROUND: { + closeAllNodeDetails(); + this.__emitChange(); + break; + } + case ActionTypes.CLICK_CLOSE_DETAILS: { + closeNodeDetails(payload.nodeId); + this.__emitChange(); + break; + } + case ActionTypes.CLICK_CLOSE_TERMINAL: { + controlPipes = controlPipes.clear(); + this.__emitChange(); + break; + } + case ActionTypes.CLICK_FORCE_RELAYOUT: { + forceRelayout = true; + // fire only once, reset after emitChange + setTimeout(() => { + forceRelayout = false; + }, 0); + this.__emitChange(); + break; + } + case ActionTypes.CLICK_NODE: { + const prevSelectedNodeId = selectedNodeId; + const prevDetailsStackSize = nodeDetails.size; + // click on sibling closes all + closeAllNodeDetails(); + // select new node if it's not the same (in that case just delesect) + if (prevDetailsStackSize > 1 || prevSelectedNodeId !== payload.nodeId) { + // dont set origin if a node was already selected, suppresses animation + const origin = prevSelectedNodeId === null ? payload.origin : null; + nodeDetails = nodeDetails.set( + payload.nodeId, + { + id: payload.nodeId, + label: payload.label, + origin, + topologyId: currentTopologyId + } + ); + selectedNodeId = payload.nodeId; + } + this.__emitChange(); + break; + } + case ActionTypes.CLICK_PAUSE_UPDATE: { + updatePausedAt = new Date; + this.__emitChange(); + break; + } + case ActionTypes.CLICK_RELATIVE: { + if (nodeDetails.has(payload.nodeId)) { + // bring to front + const details = nodeDetails.get(payload.nodeId); + nodeDetails = nodeDetails.delete(payload.nodeId); + nodeDetails = nodeDetails.set(payload.nodeId, details); + } else { + nodeDetails = nodeDetails.set( + payload.nodeId, + { + id: payload.nodeId, + label: payload.label, + origin: payload.origin, + topologyId: payload.topologyId + } + ); + } + this.__emitChange(); + break; + } + case ActionTypes.CLICK_RESUME_UPDATE: { + resumeUpdate(); + this.__emitChange(); + break; + } + case ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE: { + resumeUpdate(); + nodeDetails = nodeDetails.filter((v, k) => k === payload.nodeId); + controlPipes = controlPipes.clear(); selectedNodeId = payload.nodeId; + if (payload.topologyId !== currentTopologyId) { + setTopology(payload.topologyId); + nodes = nodes.clear(); + } + this.__emitChange(); + break; } - this.__emitChange(); - break; - - case ActionTypes.CLICK_PAUSE_UPDATE: - updatePausedAt = new Date; - this.__emitChange(); - break; - - case ActionTypes.CLICK_RELATIVE: - if (nodeDetails.has(payload.nodeId)) { - // bring to front - const details = nodeDetails.get(payload.nodeId); - nodeDetails = nodeDetails.delete(payload.nodeId); - nodeDetails = nodeDetails.set(payload.nodeId, details); - } else { - nodeDetails = nodeDetails.set( - payload.nodeId, - { - id: payload.nodeId, - label: payload.label, - origin: payload.origin, - topologyId: payload.topologyId - } - ); + case ActionTypes.CLICK_TOPOLOGY: { + resumeUpdate(); + closeAllNodeDetails(); + if (payload.topologyId !== currentTopologyId) { + setTopology(payload.topologyId); + nodes = nodes.clear(); + } + this.__emitChange(); + break; } - this.__emitChange(); - break; - - case ActionTypes.CLICK_RESUME_UPDATE: - resumeUpdate(); - this.__emitChange(); - break; - - case ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE: - resumeUpdate(); - nodeDetails = nodeDetails.filter((v, k) => k === payload.nodeId); - controlPipes = controlPipes.clear(); - selectedNodeId = payload.nodeId; - if (payload.topologyId !== currentTopologyId) { - setTopology(payload.topologyId); - nodes = nodes.clear(); + case ActionTypes.CLOSE_WEBSOCKET: { + if (!websocketClosed) { + websocketClosed = true; + this.__emitChange(); + } + break; } - this.__emitChange(); - break; - - case ActionTypes.CLICK_TOPOLOGY: - resumeUpdate(); - closeAllNodeDetails(); - if (payload.topologyId !== currentTopologyId) { - setTopology(payload.topologyId); - nodes = nodes.clear(); + case ActionTypes.DESELECT_NODE: { + closeNodeDetails(); + this.__emitChange(); + break; } - this.__emitChange(); - break; - - case ActionTypes.CLOSE_WEBSOCKET: - if (!websocketClosed) { - websocketClosed = true; + case ActionTypes.DO_CONTROL: { + controlStatus = controlStatus.set(payload.nodeId, makeMap({ + pending: true, + error: null + })); + this.__emitChange(); + break; + } + case ActionTypes.ENTER_EDGE: { + mouseOverEdgeId = payload.edgeId; + this.__emitChange(); + break; + } + case ActionTypes.ENTER_NODE: { + mouseOverNodeId = payload.nodeId; + this.__emitChange(); + break; + } + case ActionTypes.LEAVE_EDGE: { + mouseOverEdgeId = null; this.__emitChange(); + break; } - break; - - case ActionTypes.DESELECT_NODE: - closeNodeDetails(); - this.__emitChange(); - break; - - case ActionTypes.DO_CONTROL: - controlStatus = controlStatus.set(payload.nodeId, makeMap({ - pending: true, - error: null - })); - this.__emitChange(); - break; - - case ActionTypes.ENTER_EDGE: - mouseOverEdgeId = payload.edgeId; - this.__emitChange(); - break; - - case ActionTypes.ENTER_NODE: - mouseOverNodeId = payload.nodeId; - this.__emitChange(); - break; - - case ActionTypes.LEAVE_EDGE: - mouseOverEdgeId = null; - this.__emitChange(); - break; - - case ActionTypes.LEAVE_NODE: - mouseOverNodeId = null; - this.__emitChange(); - break; - - case ActionTypes.OPEN_WEBSOCKET: - // flush nodes cache after re-connect - nodes = nodes.clear(); - websocketClosed = false; - - this.__emitChange(); - break; - - case ActionTypes.DO_CONTROL_ERROR: - controlStatus = controlStatus.set(payload.nodeId, makeMap({ - pending: false, - error: payload.error - })); - this.__emitChange(); - break; - - case ActionTypes.DO_CONTROL_SUCCESS: - controlStatus = controlStatus.set(payload.nodeId, makeMap({ - pending: false, - error: null - })); - this.__emitChange(); - break; - - case ActionTypes.RECEIVE_CONTROL_PIPE: - controlPipes = controlPipes.set(payload.pipeId, makeOrderedMap({ - id: payload.pipeId, - nodeId: payload.nodeId, - raw: payload.rawTty - })); - this.__emitChange(); - break; - - case ActionTypes.RECEIVE_CONTROL_PIPE_STATUS: - if (controlPipes.has(payload.pipeId)) { - controlPipes = controlPipes.setIn([payload.pipeId, 'status'], payload.status); + case ActionTypes.LEAVE_NODE: { + mouseOverNodeId = null; this.__emitChange(); + break; } - break; + case ActionTypes.OPEN_WEBSOCKET: { + // flush nodes cache after re-connect + nodes = nodes.clear(); + websocketClosed = false; - case ActionTypes.RECEIVE_ERROR: - if (errorUrl !== null) { - errorUrl = payload.errorUrl; this.__emitChange(); + break; + } + case ActionTypes.DO_CONTROL_ERROR: { + controlStatus = controlStatus.set(payload.nodeId, makeMap({ + pending: false, + error: payload.error + })); + this.__emitChange(); + break; + } + case ActionTypes.DO_CONTROL_SUCCESS: { + controlStatus = controlStatus.set(payload.nodeId, makeMap({ + pending: false, + error: null + })); + this.__emitChange(); + break; + } + case ActionTypes.RECEIVE_CONTROL_PIPE: { + controlPipes = controlPipes.set(payload.pipeId, makeOrderedMap({ + id: payload.pipeId, + nodeId: payload.nodeId, + raw: payload.rawTty + })); + this.__emitChange(); + break; + } + case ActionTypes.RECEIVE_CONTROL_PIPE_STATUS: { + if (controlPipes.has(payload.pipeId)) { + controlPipes = controlPipes.setIn([payload.pipeId, 'status'], payload.status); + this.__emitChange(); + } + break; + } + case ActionTypes.RECEIVE_ERROR: { + if (errorUrl !== null) { + errorUrl = payload.errorUrl; + this.__emitChange(); + } + break; + } + case ActionTypes.RECEIVE_NODE_DETAILS: { + errorUrl = null; + + // disregard if node is not selected anymore + if (nodeDetails.has(payload.details.id)) { + nodeDetails = nodeDetails.update(payload.details.id, obj => { + const result = Object.assign({}, obj); + result.notFound = false; + result.details = payload.details; + return result; + }); + } + this.__emitChange(); + break; } - break; + case ActionTypes.RECEIVE_NODES_DELTA: { + const emptyMessage = !payload.delta.add && !payload.delta.remove + && !payload.delta.update; + // this action is called frequently, good to check if something changed + const emitChange = !emptyMessage || errorUrl !== null; + + if (!emptyMessage) { + log('RECEIVE_NODES_DELTA', + 'remove', _.size(payload.delta.remove), + 'update', _.size(payload.delta.update), + 'add', _.size(payload.delta.add)); + } - case ActionTypes.RECEIVE_NODE_DETAILS: - errorUrl = null; + errorUrl = null; - // disregard if node is not selected anymore - if (nodeDetails.has(payload.details.id)) { - nodeDetails = nodeDetails.update(payload.details.id, obj => { - obj.notFound = false; - obj.details = payload.details; - return obj; + // nodes that no longer exist + _.each(payload.delta.remove, (nodeId) => { + // in case node disappears before mouseleave event + if (mouseOverNodeId === nodeId) { + mouseOverNodeId = null; + } + if (nodes.has(nodeId) && _.includes(mouseOverEdgeId, nodeId)) { + mouseOverEdgeId = null; + } + nodes = nodes.delete(nodeId); }); - } - this.__emitChange(); - break; - case ActionTypes.RECEIVE_NODES_DELTA: - const emptyMessage = !payload.delta.add && !payload.delta.remove - && !payload.delta.update; - // this action is called frequently, good to check if something changed - const emitChange = !emptyMessage || errorUrl !== null; - - if (!emptyMessage) { - log('RECEIVE_NODES_DELTA', - 'remove', _.size(payload.delta.remove), - 'update', _.size(payload.delta.update), - 'add', _.size(payload.delta.add)); - } + // update existing nodes + _.each(payload.delta.update, (node) => { + if (nodes.has(node.id)) { + nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node))); + } + }); - errorUrl = null; + // add new nodes + _.each(payload.delta.add, (node) => { + nodes = nodes.set(node.id, fromJS(makeNode(node))); + }); - // nodes that no longer exist - _.each(payload.delta.remove, function(nodeId) { - // in case node disappears before mouseleave event - if (mouseOverNodeId === nodeId) { - mouseOverNodeId = null; + if (emitChange) { + this.__emitChange(); } - if (nodes.has(nodeId) && _.includes(mouseOverEdgeId, nodeId)) { - mouseOverEdgeId = null; + break; + } + case ActionTypes.RECEIVE_NOT_FOUND: { + if (nodeDetails.has(payload.nodeId)) { + nodeDetails = nodeDetails.update(payload.nodeId, obj => { + const result = Object.assign({}, obj); + result.notFound = true; + return result; + }); + this.__emitChange(); } - nodes = nodes.delete(nodeId); - }); - - // update existing nodes - _.each(payload.delta.update, function(node) { - if (nodes.has(node.id)) { - nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node))); + break; + } + case ActionTypes.RECEIVE_TOPOLOGIES: { + errorUrl = null; + topologyUrlsById = topologyUrlsById.clear(); + processTopologies(payload.topologies); + setTopology(currentTopologyId); + // only set on first load, if options are not already set via route + if (!topologiesLoaded && topologyOptions.size === 0) { + setDefaultTopologyOptions(topologies); } - }); - - // add new nodes - _.each(payload.delta.add, function(node) { - nodes = nodes.set(node.id, fromJS(makeNode(node))); - }); - - if (emitChange) { + topologiesLoaded = true; this.__emitChange(); + break; } - break; - - case ActionTypes.RECEIVE_NOT_FOUND: - if (nodeDetails.has(payload.nodeId)) { - nodeDetails = nodeDetails.update(payload.nodeId, obj => { - obj.notFound = true; - return obj; - }); + case ActionTypes.RECEIVE_API_DETAILS: { + errorUrl = null; + hostname = payload.hostname; + version = payload.version; this.__emitChange(); + break; } - break; - - case ActionTypes.RECEIVE_TOPOLOGIES: - errorUrl = null; - topologyUrlsById = topologyUrlsById.clear(); - processTopologies(payload.topologies); - setTopology(currentTopologyId); - // only set on first load, if options are not already set via route - if (!topologiesLoaded && topologyOptions.size === 0) { + case ActionTypes.ROUTE_TOPOLOGY: { + routeSet = true; + if (currentTopologyId !== payload.state.topologyId) { + nodes = nodes.clear(); + } + setTopology(payload.state.topologyId); setDefaultTopologyOptions(topologies); + selectedNodeId = payload.state.selectedNodeId; + if (payload.state.controlPipe) { + controlPipes = makeOrderedMap({ + [payload.state.controlPipe.id]: + makeOrderedMap(payload.state.controlPipe) + }); + } else { + controlPipes = controlPipes.clear(); + } + if (payload.state.nodeDetails) { + nodeDetails = makeOrderedMap(payload.state.nodeDetails.map(obj => [obj.id, obj])); + } else { + nodeDetails = nodeDetails.clear(); + } + topologyOptions = fromJS(payload.state.topologyOptions) + || topologyOptions; + this.__emitChange(); + break; } - topologiesLoaded = true; - this.__emitChange(); - break; - - case ActionTypes.RECEIVE_API_DETAILS: - errorUrl = null; - hostname = payload.hostname; - version = payload.version; - this.__emitChange(); - break; - - case ActionTypes.ROUTE_TOPOLOGY: - routeSet = true; - if (currentTopologyId !== payload.state.topologyId) { - nodes = nodes.clear(); - } - setTopology(payload.state.topologyId); - setDefaultTopologyOptions(topologies); - selectedNodeId = payload.state.selectedNodeId; - if (payload.state.controlPipe) { - controlPipes = makeOrderedMap({ - [payload.state.controlPipe.id]: - makeOrderedMap(payload.state.controlPipe) - }); - } else { - controlPipes = controlPipes.clear(); - } - if (payload.state.nodeDetails) { - nodeDetails = makeOrderedMap(payload.state.nodeDetails.map(obj => [obj.id, obj])); - } else { - nodeDetails = nodeDetails.clear(); + default: { + break; } - topologyOptions = fromJS(payload.state.topologyOptions) - || topologyOptions; - this.__emitChange(); - break; - - default: - break; - } } } diff --git a/client/app/scripts/terminal-main.js b/client/app/scripts/terminal-main.js index 0bb7e2ebb4..8e99c525de 100644 --- a/client/app/scripts/terminal-main.js +++ b/client/app/scripts/terminal-main.js @@ -5,4 +5,4 @@ import ReactDOM from 'react-dom'; import { TerminalApp } from './components/terminal-app.js'; -ReactDOM.render(, document.getElementById('app')); +ReactDOM.render(, document.getElementById('app')); diff --git a/client/app/scripts/utils/__tests__/string-utils-test.js b/client/app/scripts/utils/__tests__/string-utils-test.js index dea22e26a0..2426d6a352 100644 --- a/client/app/scripts/utils/__tests__/string-utils-test.js +++ b/client/app/scripts/utils/__tests__/string-utils-test.js @@ -1,12 +1,12 @@ jest.dontMock('../string-utils'); -describe('StringUtils', function() { +describe('StringUtils', () => { const StringUtils = require('../string-utils'); - describe('formatMetric', function() { + describe('formatMetric', () => { const formatMetric = StringUtils.formatMetric; - it('it should render 0', function() { + it('it should render 0', () => { expect(formatMetric(0)).toBe('0.00'); }); }); diff --git a/client/app/scripts/utils/__tests__/web-api-utils-test.js b/client/app/scripts/utils/__tests__/web-api-utils-test.js index d0820085bf..2609a03649 100644 --- a/client/app/scripts/utils/__tests__/web-api-utils-test.js +++ b/client/app/scripts/utils/__tests__/web-api-utils-test.js @@ -1,24 +1,24 @@ jest.dontMock('../web-api-utils'); -describe('WebApiUtils', function() { +describe('WebApiUtils', () => { const WebApiUtils = require('../web-api-utils'); - describe('basePath', function() { + describe('basePath', () => { const basePath = WebApiUtils.basePath; - it('should handle /scope/terminal.html', function() { + it('should handle /scope/terminal.html', () => { expect(basePath('/scope/terminal.html')).toBe('/scope'); }); - it('should handle /scope/', function() { + it('should handle /scope/', () => { expect(basePath('/scope/')).toBe('/scope'); }); - it('should handle /scope', function() { + it('should handle /scope', () => { expect(basePath('/scope')).toBe('/scope'); }); - it('should handle /', function() { + it('should handle /', () => { expect(basePath('/')).toBe(''); }); }); diff --git a/client/app/scripts/utils/color-utils.js b/client/app/scripts/utils/color-utils.js index f6c9d86dca..fb3885b533 100644 --- a/client/app/scripts/utils/color-utils.js +++ b/client/app/scripts/utils/color-utils.js @@ -15,9 +15,8 @@ const letterRange = endLetterRange - startLetterRange; function text2degree(text) { const input = text.substr(0, 2).toUpperCase(); let num = 0; - let charCode; for (let i = 0; i < input.length; i++) { - charCode = Math.max(Math.min(input[i].charCodeAt(), endLetterRange), startLetterRange); + const charCode = Math.max(Math.min(input[i].charCodeAt(), endLetterRange), startLetterRange); num += Math.pow(letterRange, input.length - i - 1) * (charCode - startLetterRange); } hueScale.domain([0, Math.pow(letterRange, input.length)]); diff --git a/client/app/scripts/utils/file-utils.js b/client/app/scripts/utils/file-utils.js index 762865fd32..a2be9f77c6 100644 --- a/client/app/scripts/utils/file-utils.js +++ b/client/app/scripts/utils/file-utils.js @@ -8,21 +8,20 @@ const prefix = { svg: 'http://www.w3.org/2000/svg' }; const cssSkipValues = { - 'auto': true, + auto: true, '0px 0px': true, - 'visible': true, - 'pointer': true + visible: true, + pointer: true }; function setInlineStyles(svg, target, emptySvgDeclarationComputed) { function explicitlySetStyle(element, targetEl) { const cSSStyleDeclarationComputed = getComputedStyle(element); - let value; let computedStyleStr = ''; _.each(cSSStyleDeclarationComputed, key => { - value = cSSStyleDeclarationComputed.getPropertyValue(key); + const value = cSSStyleDeclarationComputed.getPropertyValue(key); if (value !== emptySvgDeclarationComputed.getPropertyValue(key) && !cssSkipValues[value]) { - computedStyleStr += key + ':' + value + ';'; + computedStyleStr += `${key}:${value};`; } }); targetEl.setAttribute('style', computedStyleStr); @@ -70,23 +69,22 @@ function download(source, name) { if (name) { filename = name; } else if (window.document.title) { - filename = window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase() - + '-' + (+new Date); + filename = `${window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${(+new Date)}`; } const url = window.URL.createObjectURL(new Blob(source, - {'type': 'text\/xml'} + {type: 'text\/xml'} )); const a = document.createElement('a'); document.body.appendChild(a); a.setAttribute('class', 'svg-crowbar'); - a.setAttribute('download', filename + '.svg'); + a.setAttribute('download', `${filename}.svg`); a.setAttribute('href', url); a.style.display = 'none'; a.click(); - setTimeout(function() { + setTimeout(() => { window.URL.revokeObjectURL(url); }, 10); } @@ -120,7 +118,7 @@ function getSVG(doc, emptySvgDeclarationComputed) { function cleanup() { const crowbarElements = document.querySelectorAll('.svg-crowbar'); - [].forEach.call(crowbarElements, function(el) { + [].forEach.call(crowbarElements, (el) => { el.parentNode.removeChild(el); }); diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js index b2a4e32080..6878b5ac6e 100644 --- a/client/app/scripts/utils/router-utils.js +++ b/client/app/scripts/utils/router-utils.js @@ -23,17 +23,17 @@ export function updateRoute() { if (shouldReplaceState(prevState, state)) { // Replace the top of the history rather than pushing on a new item. - page.replace('/state/' + stateUrl, state, dispatch); + page.replace(`/state/${stateUrl}`, state, dispatch); } else { - page.show('/state/' + stateUrl, state, dispatch); + page.show(`/state/${stateUrl}`, state, dispatch); } } -page('/', function() { +page('/', () => { updateRoute(); }); -page('/state/:state', function(ctx) { +page('/state/:state', (ctx) => { const state = JSON.parse(ctx.params.state); route(state); }); diff --git a/client/app/scripts/utils/topology-utils.js b/client/app/scripts/utils/topology-utils.js index db08975924..4489aa5e4a 100644 --- a/client/app/scripts/utils/topology-utils.js +++ b/client/app/scripts/utils/topology-utils.js @@ -10,34 +10,30 @@ export function findTopologyById(subTree, topologyId) { if (!foundTopology && topology.has('sub_topologies')) { foundTopology = findTopologyById(topology.get('sub_topologies'), topologyId); } - if (foundTopology) { - return false; - } }); return foundTopology; } - export function updateNodeDegrees(nodes, edges) { return nodes.map(node => { const nodeId = node.get('id'); - const degree = edges.count(edge => { - return edge.get('source') === nodeId || edge.get('target') === nodeId; - }); + const degree = edges.count(edge => edge.get('source') === nodeId + || edge.get('target') === nodeId); return node.set('degree', degree); }); } /* set topology.id in place on each topology */ export function updateTopologyIds(topologies) { - topologies.forEach(topology => { - topology.id = topology.url.split('/').pop(); + return topologies.map(topology => { + const result = Object.assign({}, topology); + result.id = topology.url.split('/').pop(); if (topology.sub_topologies) { - updateTopologyIds(topology.sub_topologies); + result.sub_topologies = updateTopologyIds(topology.sub_topologies); } + return result; }); - return topologies; } // adds ID field to topology (based on last part of URL path) and save urls in diff --git a/client/app/scripts/utils/update-buffer-utils.js b/client/app/scripts/utils/update-buffer-utils.js index fd96e2dfb3..52543695f8 100644 --- a/client/app/scripts/utils/update-buffer-utils.js +++ b/client/app/scripts/utils/update-buffer-utils.js @@ -46,13 +46,14 @@ function consolidateBuffer() { let toAdd = _.union(first.add, second.add); let toUpdate = _.union(first.update, second.update); let toRemove = _.union(first.remove, second.remove); - log('Consolidating delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove)); + log('Consolidating delta buffer', 'add', _.size(toAdd), 'update', + _.size(toUpdate), 'remove', _.size(toRemove)); // check if an added node in first was updated in second -> add second update toAdd = _.map(toAdd, node => { - const updateNode = _.find(second.update, {'id': node.id}); + const updateNode = _.find(second.update, {id: node.id}); if (updateNode) { - toUpdate = _.reject(toUpdate, {'id': node.id}); + toUpdate = _.reject(toUpdate, {id: node.id}); return updateNode; } return node; @@ -63,18 +64,18 @@ function consolidateBuffer() { // check if an added node in first was removed in second -> dont add, dont remove _.each(first.add, node => { - const removedNode = _.find(second.remove, {'id': node.id}); + const removedNode = _.find(second.remove, {id: node.id}); if (removedNode) { - toAdd = _.reject(toAdd, {'id': node.id}); - toRemove = _.reject(toRemove, {'id': node.id}); + toAdd = _.reject(toAdd, {id: node.id}); + toRemove = _.reject(toRemove, {id: node.id}); } }); // check if an updated node in first was removed in second -> remove _.each(first.update, node => { - const removedNode = _.find(second.remove, {'id': node.id}); + const removedNode = _.find(second.remove, {id: node.id}); if (removedNode) { - toUpdate = _.reject(toUpdate, {'id': node.id}); + toUpdate = _.reject(toUpdate, {id: node.id}); } }); @@ -82,7 +83,8 @@ function consolidateBuffer() { // remove -> add is fine for the store // update buffer - log('Consolidated delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove)); + log('Consolidated delta buffer', 'add', _.size(toAdd), 'update', + _.size(toUpdate), 'remove', _.size(toRemove)); deltaBuffer.set(0, { add: toAdd.length > 0 ? toAdd : null, update: toUpdate.length > 0 ? toUpdate : null, diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js index eb593bec3d..179dcabfb8 100644 --- a/client/app/scripts/utils/web-api-utils.js +++ b/client/app/scripts/utils/web-api-utils.js @@ -23,9 +23,7 @@ let controlErrorTimer = 0; function buildOptionsQuery(options) { if (options) { - return options.reduce(function(query, value, param) { - return `${query}&${param}=${value}`; - }, ''); + return options.reduce((query, value, param) => `${query}&${param}=${value}`, ''); } return ''; } @@ -52,11 +50,11 @@ export function basePathSlash(urlPath) { // "/scope" -> "/scope/" // "/" -> "/" // - return basePath(urlPath) + '/'; + return `${basePath(urlPath)}/`; } const wsProto = location.protocol === 'https:' ? 'wss' : 'ws'; -const wsUrl = wsProto + '://' + location.host + basePath(location.pathname); +const wsUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`; function createWebsocket(topologyUrl, optionsQuery) { if (socket) { @@ -67,30 +65,29 @@ function createWebsocket(topologyUrl, optionsQuery) { // right away } - socket = new WebSocket(wsUrl + topologyUrl - + '/ws?t=' + updateFrequency + '&' + optionsQuery); + socket = new WebSocket(`${wsUrl}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`); - socket.onopen = function() { + socket.onopen = () => { openWebsocket(); }; - socket.onclose = function() { + socket.onclose = () => { clearTimeout(reconnectTimer); - log('Closing websocket to ' + topologyUrl, socket.readyState); + log(`Closing websocket to ${topologyUrl}`, socket.readyState); socket = null; closeWebsocket(); - reconnectTimer = setTimeout(function() { + reconnectTimer = setTimeout(() => { createWebsocket(topologyUrl, optionsQuery); }, reconnectTimerInterval); }; - socket.onerror = function() { - log('Error in websocket to ' + topologyUrl); + socket.onerror = () => { + log(`Error in websocket to ${topologyUrl}`); receiveError(currentUrl); }; - socket.onmessage = function(event) { + socket.onmessage = (event) => { const msg = JSON.parse(event.data); receiveNodesDelta(msg); }; @@ -103,17 +100,17 @@ export function getTopologies(options) { const optionsQuery = buildOptionsQuery(options); const url = `api/topology?${optionsQuery}`; reqwest({ - url: url, - success: function(res) { + url, + success: (res) => { receiveTopologies(res); - topologyTimer = setTimeout(function() { + topologyTimer = setTimeout(() => { getTopologies(options); }, TOPOLOGY_INTERVAL); }, - error: function(err) { - log('Error in topology request: ' + err.responseText); + error: (err) => { + log(`Error in topology request: ${err.responseText}`); receiveError(url); - topologyTimer = setTimeout(function() { + topologyTimer = setTimeout(() => { getTopologies(options); }, TOPOLOGY_INTERVAL / 2); } @@ -139,15 +136,15 @@ export function getNodeDetails(topologyUrlsById, nodeMap) { const url = [topologyUrl, '/', encodeURIComponent(obj.id)] .join('').substr(1); reqwest({ - url: url, - success: function(res) { + url, + success: (res) => { // make sure node is still selected if (nodeMap.has(res.node.id)) { receiveNodeDetails(res.node); } }, - error: function(err) { - log('Error in node details request: ' + err.responseText); + error: (err) => { + log(`Error in node details request: ${err.responseText}`); // dont treat missing node as error if (err.status === 404) { receiveNotFound(obj.id); @@ -165,13 +162,13 @@ export function getApiDetails() { clearTimeout(apiDetailsTimer); const url = 'api'; reqwest({ - url: url, - success: function(res) { + url, + success: (res) => { receiveApiDetails(res); apiDetailsTimer = setTimeout(getApiDetails, API_INTERVAL); }, - error: function(err) { - log('Error in api details request: ' + err.responseText); + error: (err) => { + log(`Error in api details request: ${err.responseText}`); receiveError(url); apiDetailsTimer = setTimeout(getApiDetails, API_INTERVAL / 2); } @@ -184,16 +181,16 @@ export function doControlRequest(nodeId, control) { + `${encodeURIComponent(control.nodeId)}/${control.id}`; reqwest({ method: 'POST', - url: url, - success: function(res) { + url, + success: (res) => { receiveControlSuccess(nodeId); if (res && res.pipe) { receiveControlPipe(res.pipe, nodeId, res.raw_tty, true); } }, - error: function(err) { + error: (err) => { receiveControlError(nodeId, err.response); - controlErrorTimer = setTimeout(function() { + controlErrorTimer = setTimeout(() => { clearControlError(nodeId); }, 10000); } @@ -204,12 +201,12 @@ export function deletePipe(pipeId) { const url = `api/pipe/${encodeURIComponent(pipeId)}`; reqwest({ method: 'DELETE', - url: url, - success: function() { + url, + success: () => { log('Closed the pipe!'); }, - error: function(err) { - log('Error closing pipe:' + err); + error: (err) => { + log(`Error closing pipe:${err}`); receiveError(url); } }); @@ -219,11 +216,11 @@ export function getPipeStatus(pipeId) { const url = `api/pipe/${encodeURIComponent(pipeId)}/check`; reqwest({ method: 'GET', - url: url, - error: function(err) { + url, + error: (err) => { log('ERROR: unexpected response:', err); }, - success: function(res) { + success: (res) => { const status = { 200: 'PIPE_ALIVE', 204: 'PIPE_DELETED' diff --git a/client/package.json b/client/package.json index bc69d7cc0c..eb65ddaeea 100644 --- a/client/package.json +++ b/client/package.json @@ -29,36 +29,36 @@ "timely": "0.1.0" }, "devDependencies": { - "autoprefixer": "6.0.1", - "babel-core": "6.1.4", - "babel-eslint": "4.1.5", - "babel-jest": "6.0.1", - "babel-loader": "6.1.0", - "babel-preset-es2015": "6.1.4", - "babel-preset-react": "6.1.4", - "css-loader": "0.22.0", - "eslint": "1.9.0", - "eslint-config-airbnb": "1.0.0", - "eslint-loader": "1.1.1", + "autoprefixer": "6.3.3", + "babel-core": "6.7.2", + "babel-eslint": "5.0.0", + "babel-jest": "9.0.3", + "babel-loader": "6.2.4", + "babel-preset-es2015": "6.6.0", + "babel-preset-react": "6.5.0", + "css-loader": "0.23.1", + "eslint": "2.4.0", + "eslint-config-airbnb": "6.1.0", + "eslint-loader": "1.3.0", "eslint-plugin-jasmine": "1.6.0", - "eslint-plugin-react": "3.8.0", - "file-loader": "0.8.4", + "eslint-plugin-react": "4.2.2", + "file-loader": "0.8.5", "http-proxy-rules": "^1.0.1", - "jest-cli": "~0.7.1", - "json-loader": "0.5.3", - "less": "~2.5.1", - "less-loader": "2.2.1", - "postcss-loader": "0.7.0", + "jest-cli": "~0.9.2", + "json-loader": "0.5.4", + "less": "~2.6.1", + "less-loader": "2.2.2", + "postcss-loader": "0.8.2", "style-loader": "0.13.0", "url": "0.11.0", - "url-loader": "0.5.6", + "url-loader": "0.5.7", "webpack": "~1.12.4" }, "optionalDependencies": { "express": "~4.13.3", "http-proxy": "^1.12.0", "react-hot-loader": "~1.3.0", - "webpack-dev-server": "~1.12.1" + "webpack-dev-server": "~1.14.1" }, "scripts": { "build": "webpack --config webpack.production.config.js",