Revert "Upgraded eslint & eslint-config-airbnb"

This commit is contained in:
David
2016-12-12 16:06:13 +01:00
committed by GitHub
parent 0921af04d5
commit 99bfab89b7
71 changed files with 506 additions and 640 deletions

View File

@@ -1 +1,2 @@
app/scripts/vendor/term.js
test/

View File

@@ -7,32 +7,11 @@
},
"rules": {
"comma-dangle": 0,
"global-require": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true,
"optionalDependencies": true,
"peerDependencies": true
}
],
"import/prefer-default-export": 0,
"jsx-a11y/no-static-element-interactions": 0,
"no-param-reassign": 0,
"no-restricted-properties": 0,
"object-curly-spacing": 0,
"react/jsx-closing-bracket-location": 0,
"react/jsx-filename-extension": [
2,
{
"extensions": [
".js",
".jsx"
]
}
],
"react/prefer-stateless-function": 0,
"react/sort-comp": 0,
"react/prop-types": 0,
"react/prop-types": 0
}
}

View File

@@ -39,8 +39,7 @@ export function sortOrderChanged(sortedBy, sortedDesc) {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.SORT_ORDER_CHANGED,
sortedBy,
sortedDesc
sortedBy, sortedDesc
});
updateRoute(getState);
};

View File

@@ -9,7 +9,7 @@ describe('NodesLayout', () => {
const coords = [];
nodes
.sortBy(node => node.get('id'))
.forEach((node) => {
.forEach(node => {
coords.push(node.get('x'));
coords.push(node.get('y'));
});

View File

@@ -17,8 +17,7 @@ class Edge extends React.Component {
const className = classNames('edge', {highlighted, blurred, focused});
return (
<g
className={className} onMouseEnter={this.handleMouseEnter}
<g className={className} onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave} id={id}>
<path d={path} className="shadow" />
<path d={path} className="link" />

View File

@@ -14,13 +14,12 @@ class NodeContainer extends React.Component {
const other = omit(this.props, 'dx', 'dy');
return (
<Motion
style={{
x: spring(dx, animConfig),
y: spring(dy, animConfig),
f: spring(scaleFactor, animConfig)
}}>
{(interpolated) => {
<Motion style={{
x: spring(dx, animConfig),
y: spring(dy, animConfig),
f: spring(scaleFactor, animConfig)
}}>
{interpolated => {
const transform = `translate(${round(interpolated.x, layoutPrecision)},`
+ `${round(interpolated.y, layoutPrecision)})`;
return <Node {...other} transform={transform} scaleFactor={interpolated.f} />;

View File

@@ -26,7 +26,7 @@ function NodeNetworksOverlay({offset, size, stack, networks = makeList()}) {
const bars = networks.map((n, i) => (
<rect
x={x(i)}
y={offset - (barHeight * 0.5)}
y={offset - barHeight * 0.5}
width={x.bandwidth()}
height={barHeight}
rx={rx}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils';
import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles';
import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
export default function NodeShapeCircle({id, highlighted, size, color, metric}) {
@@ -17,12 +17,8 @@ export default function NodeShapeCircle({id, highlighted, size, color, metric})
{highlighted && <circle r={size * 0.7} className="highlighted" />}
<circle r={size * 0.5} className="border" stroke={color} />
<circle r={size * 0.45} className="shadow" />
{hasMetric && <circle
r={size * 0.45}
className="metric-fill"
style={metricStyle}
clipPath={`url(#${clipId})`}
/>}
{hasMetric && <circle r={size * 0.45} className="metric-fill" style={metricStyle}
clipPath={`url(#${clipId})`} />}
{highlighted && hasMetric ?
<text style={{fontSize}}>{formattedValue}</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}

View File

@@ -37,7 +37,8 @@ export default function NodeShapeCloud({highlighted, size, color}) {
return (
<g className="shape shape-cloud">
{highlighted && <path className="highlighted" {...pathProps(0.7)} />}
{highlighted &&
<path className="highlighted" {...pathProps(0.7)} />}
<path className="border" stroke={color} {...pathProps(0.5)} />
<path className="shadow" {...pathProps(0.45)} />
<circle className="node" r={Math.max(2, (size * 0.125))} />

View File

@@ -1,8 +1,8 @@
import React from 'react';
import classNames from 'classnames';
import { line, curveCardinalClosed } from 'd3-shape';
import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils';
import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles';
import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils.js';
import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles.js';
const spline = line()
@@ -12,7 +12,7 @@ const spline = line()
function polygon(r, sides) {
const a = (Math.PI * 2) / sides;
const points = [];
for (let i = 0; i < sides; i += 1) {
for (let i = 0; i < sides; i++) {
points.push([r * Math.sin(a * i), -r * Math.cos(a * i)]);
}
return points;
@@ -30,20 +30,15 @@ export default function NodeShapeHeptagon({id, highlighted, size, color, metric}
const metricStyle = { fill: getMetricColor(metric) };
const className = classNames('shape', { metrics: hasMetric });
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
const halfSize = size * 0.5;
return (
<g className={className}>
{hasMetric && getClipPathDefinition(clipId, size, height, -halfSize, halfSize - height)}
{hasMetric && getClipPathDefinition(clipId, size, height, -size * 0.5, size * 0.5 - height)}
{highlighted && <path className="highlighted" {...pathProps(0.7)} />}
<path className="border" stroke={color} {...pathProps(0.5)} />
<path className="shadow" {...pathProps(0.45)} />
{hasMetric && <path
className="metric-fill"
clipPath={`url(#${clipId})`}
style={metricStyle}
{...pathProps(0.45)}
/>}
{hasMetric && <path className="metric-fill" clipPath={`url(#${clipId})`}
style={metricStyle} {...pathProps(0.45)} />}
{highlighted && hasMetric ?
<text style={{fontSize}}>{formattedValue}</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}

View File

@@ -1,8 +1,8 @@
import React from 'react';
import classNames from 'classnames';
import { line, curveCardinalClosed } from 'd3-shape';
import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils';
import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles';
import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils.js';
import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles.js';
const spline = line()
@@ -48,22 +48,13 @@ export default function NodeShapeHexagon({id, highlighted, size, color, metric})
return (
<g className={className}>
{hasMetric && getClipPathDefinition(
clipId,
size * (1 + (hexCurve * 2)),
height,
-(size * hexCurve),
(size - height) * (shadowSize * 2)
)}
{hasMetric && getClipPathDefinition(clipId,
size * (1 + hexCurve * 2), height, -size * hexCurve, (size - height) * shadowSize * 2)}
{highlighted && <path className="highlighted" {...pathProps(0.7)} />}
<path className="border" stroke={color} {...pathProps(0.5)} />
<path className="shadow" {...pathProps(shadowSize)} />
{hasMetric && <path
className="metric-fill"
style={metricStyle}
clipPath={`url(#${clipId})`}
{...pathProps(shadowSize)}
/>}
{hasMetric && <path className="metric-fill" style={metricStyle}
clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />}
{highlighted && hasMetric ?
<text style={{fontSize}}>
{formattedValue}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils';
import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles';
import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
export default function NodeShapeSquare({
@@ -28,11 +28,8 @@ export default function NodeShapeSquare({
{highlighted && <rect className="highlighted" {...rectProps(0.7)} />}
<rect className="border" stroke={color} {...rectProps(0.5, 0.5)} />
<rect className="shadow" {...rectProps(0.45, 0.39)} />
{hasMetric && <rect
className="metric-fill" style={metricStyle}
clipPath={`url(#${clipId})`}
{...rectProps(0.45, 0.39)}
/>}
{hasMetric && <rect className="metric-fill" style={metricStyle}
clipPath={`url(#${clipId})`} {...rectProps(0.45, 0.39)} />}
{highlighted && hasMetric ?
<text style={{fontSize}}>
{formattedValue}

View File

@@ -5,8 +5,8 @@ export default function NodeShapeStack(props) {
const contrastMode = isContrastMode();
const Shape = props.shape;
const [dx, dy] = contrastMode ? [0, 8] : [0, 5];
const dsx = (props.size + dx) / props.size;
const dsy = (props.size + dy) / props.size;
const dsx = (props.size * 2 + (dx * 2)) / (props.size * 2);
const dsy = (props.size * 2 + (dy * 2)) / (props.size * 2);
const hls = [dsx, dsy];
return (

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Map as makeMap, List as makeList } from 'immutable';
@@ -126,12 +127,9 @@ class Node extends React.Component {
svgLabels(label, subLabel, labelClassName, subLabelClassName, labelOffsetY) :
<foreignObject
style={{pointerEvents: 'none'}}
x={labelOffsetX} y={labelOffsetY}
<foreignObject style={{pointerEvents: 'none'}} x={labelOffsetX} y={labelOffsetY}
width={labelWidth} height="100em">
<div
className="node-label-wrapper"
<div className="node-label-wrapper"
style={{pointerEvents: 'all', fontSize, maxWidth: labelWidth}}
{...mouseEvents}>
<div className={labelClassName}>
@@ -151,11 +149,8 @@ class Node extends React.Component {
{...this.props} />
</g>
{showingNetworks && <NodeNetworksOverlay
offset={networkOffset}
size={size} networks={networks}
stack={stack}
/>}
{showingNetworks && <NodeNetworksOverlay offset={networkOffset}
size={size} networks={networks} stack={stack} />}
</g>
);
}
@@ -166,7 +161,8 @@ class Node extends React.Component {
handleMouseClick(ev) {
ev.stopPropagation();
this.props.clickNode(this.props.id, this.props.label, this.shapeRef.getBoundingClientRect());
this.props.clickNode(this.props.id, this.props.label,
ReactDOM.findDOMNode(this.shapeRef).getBoundingClientRect());
}
handleMouseEnter() {

View File

@@ -13,7 +13,7 @@ class NodesChartEdges extends React.Component {
return (
<g className="nodes-chart-edges">
{layoutEdges.toIndexedSeq().map((edge) => {
{layoutEdges.toIndexedSeq().map(edge => {
const sourceSelected = selectedNodeId === edge.get('source');
const targetSelected = selectedNodeId === edge.get('target');
const highlighted = highlightedEdgeIds.has(edge.get('id'));
@@ -26,8 +26,8 @@ class NodesChartEdges extends React.Component {
!(selectedNetworkNodes.contains(edge.get('source')) &&
selectedNetworkNodes.contains(edge.get('target')));
const blurred = !highlighted && (otherNodesSelected ||
(!focused && noMatches) ||
(!focused && noSelectedNetworks));
!focused && noMatches ||
!focused && noSelectedNetworks);
return (
<EdgeContainer

View File

@@ -21,12 +21,14 @@ class NodesChartNodes extends React.Component {
&& (selectedNodeId === node.get('id')
|| (adjacentNodes && adjacentNodes.includes(node.get('id')))));
const setBlurred = node => node.set('blurred',
(selectedNodeId && !node.get('focused'))
|| (searchQuery && !searchNodeMatches.has(node.get('id')) && !node.get('highlighted'))
|| (selectedNetwork && !(node.get('networks') || makeList()).find(n => n.get('id') === selectedNetwork)));
selectedNodeId && !node.get('focused')
|| searchQuery && !searchNodeMatches.has(node.get('id'))
&& !node.get('highlighted')
|| selectedNetwork
&& !(node.get('networks') || makeList()).find(n => n.get('id') === selectedNetwork));
// make sure blurred nodes are in the background
const sortNodes = (node) => {
const sortNodes = node => {
if (node.get('id') === mouseOverNodeId) {
return 3;
}
@@ -40,7 +42,7 @@ class NodesChartNodes extends React.Component {
};
// TODO: think about pulling this up into the store.
const metric = (node) => {
const metric = node => {
const isHighlighted = topCardNode && topCardNode.details && topCardNode.id === node.get('id');
const sourceNode = isHighlighted ? fromJS(topCardNode.details) : node;
return sourceNode.get('metrics') && sourceNode.get('metrics')

View File

@@ -52,7 +52,7 @@ function initEdges(nodes) {
nodes.forEach((node, nodeId) => {
const adjacency = node.get('adjacency');
if (adjacency) {
adjacency.forEach((adjacent) => {
adjacency.forEach(adjacent => {
const edge = [nodeId, adjacent];
const edgeId = edge.join(EDGE_ID_SEPARATOR);
@@ -115,80 +115,6 @@ function updateLayout(width, height, nodes, baseOptions) {
}
function centerSelectedNode(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
if (!stateNodes.has(props.selectedNodeId)) {
return {};
}
const adjacentNodes = props.adjacentNodes;
const adjacentLayoutNodeIds = [];
adjacentNodes.forEach((adjacentId) => {
// filter loopback
if (adjacentId !== props.selectedNodeId) {
adjacentLayoutNodeIds.push(adjacentId);
}
});
// move origin node to center of viewport
const zoomScale = state.scale;
const translate = [state.panTranslateX, state.panTranslateY];
const viewportHalfWidth = ((state.width + props.margins.left) - DETAILS_PANEL_WIDTH) / 2;
const viewportHalfHeight = (state.height + props.margins.top) / 2;
const centerX = (-translate[0] + viewportHalfWidth) / zoomScale;
const centerY = (-translate[1] + viewportHalfHeight) / zoomScale;
stateNodes = stateNodes.mergeIn([props.selectedNodeId], {
x: centerX,
y: centerY
});
// circle layout for adjacent nodes
const adjacentCount = adjacentLayoutNodeIds.length;
const density = radiusDensity(adjacentCount);
const radius = Math.min(state.width, state.height) / density / zoomScale;
const offsetAngle = Math.PI / 4;
stateNodes = stateNodes.map((node, nodeId) => {
const index = adjacentLayoutNodeIds.indexOf(nodeId);
if (index > -1) {
const angle = offsetAngle + ((Math.PI * 2 * index) / adjacentCount);
return node.merge({
x: centerX + (radius * Math.sin(angle)),
y: centerY + (radius * Math.cos(angle))
});
}
return node;
});
// fix all edges for circular nodes
stateEdges = stateEdges.map((edge) => {
if (edge.get('source') === props.selectedNodeId
|| edge.get('target') === props.selectedNodeId
|| includes(adjacentLayoutNodeIds, edge.get('source'))
|| includes(adjacentLayoutNodeIds, edge.get('target'))) {
const source = stateNodes.get(edge.get('source'));
const target = stateNodes.get(edge.get('target'));
return edge.set('points', fromJS([
{x: source.get('x'), y: source.get('y')},
{x: target.get('x'), y: target.get('y')}
]));
}
return edge;
});
// auto-scale node size for selected nodes
const selectedNodeScale = getNodeScale(adjacentNodes.size, state.width, state.height);
return {
selectedNodeScale,
edges: stateEdges,
nodes: stateNodes
};
}
class NodesChart extends React.Component {
constructor(props, context) {
@@ -254,7 +180,7 @@ class NodesChart extends React.Component {
assign(state, this.restoreLayout(state));
}
if (nextProps.selectedNodeId) {
assign(state, centerSelectedNode(nextProps, state));
assign(state, this.centerSelectedNode(nextProps, state));
}
this.setState(state);
@@ -293,8 +219,7 @@ class NodesChart extends React.Component {
const layoutPrecision = getLayoutPrecision(nodes.size);
return (
<div className="nodes-chart">
<svg
width="100%" height="100%" id="nodes-chart-canvas"
<svg width="100%" height="100%" id="nodes-chart-canvas"
className={svgClassNames} onClick={this.handleMouseClick}>
<g transform="translate(24,24) scale(0.25)">
<Logo />
@@ -320,6 +245,78 @@ class NodesChart extends React.Component {
}
}
centerSelectedNode(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
if (!stateNodes.has(props.selectedNodeId)) {
return {};
}
const adjacentNodes = props.adjacentNodes;
const adjacentLayoutNodeIds = [];
adjacentNodes.forEach(adjacentId => {
// filter loopback
if (adjacentId !== props.selectedNodeId) {
adjacentLayoutNodeIds.push(adjacentId);
}
});
// move origin node to center of viewport
const zoomScale = state.scale;
const translate = [state.panTranslateX, state.panTranslateY];
const centerX = (-translate[0] + (state.width + props.margins.left
- DETAILS_PANEL_WIDTH) / 2) / zoomScale;
const centerY = (-translate[1] + (state.height + props.margins.top) / 2) / zoomScale;
stateNodes = stateNodes.mergeIn([props.selectedNodeId], {
x: centerX,
y: centerY
});
// circle layout for adjacent nodes
const adjacentCount = adjacentLayoutNodeIds.length;
const density = radiusDensity(adjacentCount);
const radius = Math.min(state.width, state.height) / density / zoomScale;
const offsetAngle = Math.PI / 4;
stateNodes = stateNodes.map((node, nodeId) => {
const index = adjacentLayoutNodeIds.indexOf(nodeId);
if (index > -1) {
const angle = offsetAngle + Math.PI * 2 * index / adjacentCount;
return node.merge({
x: centerX + radius * Math.sin(angle),
y: centerY + radius * Math.cos(angle)
});
}
return node;
});
// fix all edges for circular nodes
stateEdges = stateEdges.map(edge => {
if (edge.get('source') === props.selectedNodeId
|| edge.get('target') === props.selectedNodeId
|| includes(adjacentLayoutNodeIds, edge.get('source'))
|| includes(adjacentLayoutNodeIds, edge.get('target'))) {
const source = stateNodes.get(edge.get('source'));
const target = stateNodes.get(edge.get('target'));
return edge.set('points', fromJS([
{x: source.get('x'), y: source.get('y')},
{x: target.get('x'), y: target.get('y')}
]));
}
return edge;
});
// auto-scale node size for selected nodes
const selectedNodeScale = getNodeScale(adjacentNodes.size, state.width, state.height);
return {
selectedNodeScale,
edges: stateEdges,
nodes: stateNodes
};
}
restoreLayout(state) {
// undo any pan/zooming that might have happened
this.setZoom(state);
@@ -329,7 +326,7 @@ class NodesChart extends React.Component {
y: node.get('py')
}));
const edges = state.edges.map((edge) => {
const edges = state.edges.map(edge => {
if (edge.has('ppoints')) {
return edge.set('points', edge.get('ppoints'));
}

View File

@@ -17,7 +17,7 @@ const IGNORED_COLUMNS = ['docker_container_ports', 'docker_container_id', 'docke
function getColumns(nodes) {
const metricColumns = nodes
.toList()
.flatMap((n) => {
.flatMap(n => {
const metrics = (n.get('metrics') || makeList())
.map(m => makeMap({ id: m.get('id'), label: m.get('label'), dataType: 'number' }));
return metrics;
@@ -28,7 +28,7 @@ function getColumns(nodes) {
const metadataColumns = nodes
.toList()
.flatMap((n) => {
.flatMap(n => {
const metadata = (n.get('metadata') || makeList())
.map(m => makeMap({ id: m.get('id'), label: m.get('label'), dataType: m.get('dataType') }));
return metadata;
@@ -40,7 +40,7 @@ function getColumns(nodes) {
const relativesColumns = nodes
.toList()
.flatMap((n) => {
.flatMap(n => {
const metadata = (n.get('parents') || makeList())
.map(m => makeMap({ id: m.get('topologyId'), label: m.get('topologyId') }));
return metadata;

View File

@@ -55,7 +55,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
});
// add nodes to the graph if not already there
nodes.forEach((node) => {
nodes.forEach(node => {
const gNodeId = graphNodeId(node.get('id'));
if (!graph.hasNode(gNodeId)) {
graph.setNode(gNodeId, {
@@ -66,7 +66,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
});
// remove nodes that are no longer there or are 0-degree nodes
graph.nodes().forEach((gNodeId) => {
graph.nodes().forEach(gNodeId => {
const nodeId = fromGraphNodeId(gNodeId);
if (!nodes.has(nodeId) || nodes.get(nodeId).get('degree') === 0) {
graph.removeNode(gNodeId);
@@ -74,7 +74,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
});
// add edges to the graph if not already there
edges.forEach((edge) => {
edges.forEach(edge => {
const s = graphNodeId(edge.get('source'));
const t = graphNodeId(edge.get('target'));
if (!graph.hasEdge(s, t)) {
@@ -84,7 +84,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
});
// remove edges that are no longer there
graph.edges().forEach((edgeObj) => {
graph.edges().forEach(edgeObj => {
const edge = [fromGraphNodeId(edgeObj.v), fromGraphNodeId(edgeObj.w)];
const edgeId = edge.join(EDGE_ID_SEPARATOR);
if (!edges.has(edgeId)) {
@@ -97,14 +97,14 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
// apply coordinates to nodes and edges
graph.nodes().forEach((gNodeId) => {
graph.nodes().forEach(gNodeId => {
const graphNode = graph.node(gNodeId);
const nodeId = fromGraphNodeId(gNodeId);
nodes = nodes.setIn([nodeId, 'x'], graphNode.x);
nodes = nodes.setIn([nodeId, 'y'], graphNode.y);
});
graph.edges().forEach((graphEdge) => {
graph.edges().forEach(graphEdge => {
const graphEdgeMeta = graph.edge(graphEdge);
const edge = edges.get(graphEdgeMeta.id);
let points = fromJS(graphEdgeMeta.points);
@@ -165,7 +165,7 @@ export function doLayoutNewNodesOfExistingRank(layout, nodeCache, opts) {
const oldNodes = ImmSet.fromKeys(nodeCache);
const newNodes = ImmSet.fromKeys(layout.nodes.filter(n => n.get('degree') > 0))
.subtract(oldNodes);
result.nodes = layout.nodes.map((n) => {
result.nodes = layout.nodes.map(n => {
if (newNodes.contains(n.get('id'))) {
const nodesSameRank = nodeCache.filter(nn => nn.get('rank') === n.get('rank'));
if (nodesSameRank.size > 0) {
@@ -178,7 +178,7 @@ export function doLayoutNewNodesOfExistingRank(layout, nodeCache, opts) {
return n;
});
result.edges = layout.edges.map((edge) => {
result.edges = layout.edges.map(edge => {
if (!edge.has('points')) {
return setSimpleEdgePoints(edge, layout.nodes);
}
@@ -237,23 +237,23 @@ function layoutSingleNodes(layout, opts) {
}
// default margins
offsetX = offsetX || (margins.left + nodeWidth) / 2;
offsetY = offsetY || (margins.top + nodeHeight) / 2;
offsetX = offsetX || margins.left + nodeWidth / 2;
offsetY = offsetY || margins.top + nodeHeight / 2;
const columns = Math.ceil(Math.sqrt(singleNodes.size));
let row = 0;
let col = 0;
let singleX;
let singleY;
nodes = nodes.sortBy(node => node.get('rank')).map((node) => {
nodes = nodes.sortBy(node => node.get('rank')).map(node => {
if (singleNodes.has(node.get('id'))) {
if (col === columns) {
col = 0;
row += 1;
row++;
}
singleX = (col * (nodesep + nodeWidth)) + offsetX;
singleY = (row * (ranksep + nodeHeight)) + offsetY;
col += 1;
singleX = col * (nodesep + nodeWidth) + offsetX;
singleY = row * (ranksep + nodeHeight) + offsetY;
col++;
return node.merge({
x: singleX,
y: singleY
@@ -263,8 +263,8 @@ function layoutSingleNodes(layout, opts) {
});
// adjust layout dimensions if graph is now bigger
result.width = Math.max(layout.width, singleX + (nodeWidth / 2) + nodesep);
result.height = Math.max(layout.height, singleY + (nodeHeight / 2) + ranksep);
result.width = Math.max(layout.width, singleX + nodeWidth / 2 + nodesep);
result.height = Math.max(layout.height, singleY + nodeHeight / 2 + ranksep);
result.nodes = nodes;
}
@@ -290,12 +290,12 @@ export function shiftLayoutToCenter(layout, opts) {
if (layout.width < width) {
const xMin = layout.nodes.minBy(n => n.get('x'));
const xMax = layout.nodes.maxBy(n => n.get('x'));
offsetX = ((width - (xMin.get('x') + xMax.get('x'))) / 2) + margins.left;
offsetX = (width - (xMin.get('x') + xMax.get('x'))) / 2 + margins.left;
}
if (layout.height < height) {
const yMin = layout.nodes.minBy(n => n.get('y'));
const yMax = layout.nodes.maxBy(n => n.get('y'));
offsetY = ((height - (yMin.get('y') + yMax.get('y'))) / 2) + margins.top;
offsetY = (height - (yMin.get('y') + yMax.get('y'))) / 2 + margins.top;
}
if (offsetX || offsetY) {
@@ -412,7 +412,7 @@ function copyLayoutProperties(layout, nodeCache, edgeCache) {
const result = Object.assign({}, layout);
result.nodes = layout.nodes.map(node => (nodeCache.has(node.get('id'))
? node.merge(nodeCache.get(node.get('id'))) : node));
result.edges = layout.edges.map((edge) => {
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')));
@@ -453,10 +453,9 @@ export function doLayout(immNodes, immEdges, opts) {
const useCache = !options.forceRelayout && cachedLayout && nodeCache && edgeCache;
let layout;
layoutRuns += 1;
++layoutRuns;
if (useCache && !hasUnseenNodes(immNodes, nodeCache)) {
layoutRunsTrivial += 1;
log('skip layout, trivial adjustment', layoutRunsTrivial, layoutRuns);
log('skip layout, trivial adjustment', ++layoutRunsTrivial, layoutRuns);
layout = cloneLayout(cachedLayout, immNodes, immEdges);
// copy old properties, works also if nodes get re-added
layout = copyLayoutProperties(layout, nodeCache, edgeCache);

View File

@@ -5,7 +5,7 @@ import { Provider } from 'react-redux';
import configureStore from '../../stores/configureStore';
// need ES5 require to keep automocking off
const NodeDetails = require('../node-details.js').default.WrappedComponent;
const NodeDetails = require('../node-details.js').NodeDetails;
describe('NodeDetails', () => {
let nodes;
@@ -34,8 +34,7 @@ describe('NodeDetails', () => {
details = {label: 'Node 1'};
const c = TestUtils.renderIntoDocument(
<Provider store={configureStore()}>
<NodeDetails
nodes={nodes}
<NodeDetails nodes={nodes}
topologyId="containers"
nodeId={nodeId} details={details}
/>

View File

@@ -3,13 +3,13 @@ import React from 'react';
import { connect } from 'react-redux';
import Logo from './logo';
import Footer from './footer';
import Sidebar from './sidebar';
import Footer from './footer.js';
import Sidebar from './sidebar.js';
import HelpPanel from './help-panel';
import Search from './search';
import Status from './status';
import Topologies from './topologies';
import TopologyOptions from './topology-options';
import Status from './status.js';
import Topologies from './topologies.js';
import TopologyOptions from './topology-options.js';
import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import { focusSearch, pinNextMetric, hitBackspace, hitEnter, hitEsc, unpinMetric,
selectMetric, toggleHelp, toggleGridMode } from '../actions/app-actions';
@@ -18,8 +18,10 @@ import Nodes from './nodes';
import GridModeSelector from './grid-mode-selector';
import MetricSelector from './metric-selector';
import NetworkSelector from './networks-selector';
import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar';
import { getRouter, getUrlState } from '../utils/router-utils';
import { getRouter } from '../utils/router-utils';
import DebugToolbar, { showingDebugToolbar,
toggleDebugToolbar } from './debug-toolbar.js';
import { getUrlState } from '../utils/router-utils';
import { getActiveTopologyOptions } from '../utils/topology-utils';
const BACKSPACE_KEY_CODE = 8;

View File

@@ -5,7 +5,9 @@ import { connect } from 'react-redux';
import { sampleSize, sample, random, range, flattenDeep } from 'lodash';
import { fromJS, Set as makeSet } from 'immutable';
import { hsl } from 'd3-color';
import debug from 'debug';
const log = debug('scope:debug-panel');
import ActionTypes from '../constants/action-types';
import { receiveNodesDelta } from '../actions/app-actions';
@@ -27,7 +29,7 @@ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occa
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
const sampleArray = (collection, n = 4) => sampleSize(collection, random(n));
const log = debug('scope:debug-panel');
const shapeTypes = {
square: ['Process', 'Processes'],
@@ -78,7 +80,7 @@ function label(shape, stacked) {
function addAllVariants(dispatch) {
const newNodes = flattenDeep(STACK_VARIANTS.map(stack => (SHAPES.map((s) => {
const newNodes = flattenDeep(STACK_VARIANTS.map(stack => (SHAPES.map(s => {
if (!stack) return [deltaAdd(label(s, stack), [], s, stack, 1)];
return NODE_COUNTS.map(n => deltaAdd(label(s, stack), [], s, stack, n));
}))));
@@ -237,16 +239,16 @@ class DebugToolbar extends React.Component {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
this.asyncDispatch(receiveNodesDelta({
add: this.createRandomNodes(7),
add: this._addNodes(7),
update: sampleArray(nodeNames).map(n => ({
id: n,
adjacency: sampleArray(nodeNames),
}), nodeNames.length),
remove: this.randomExistingNode(),
remove: this._removeNode(),
}));
}
createRandomNodes(n, prefix = 'zing') {
_addNodes(n, prefix = 'zing') {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
const newNodeNames = range(ns.size, ns.size + n).map(i => (
@@ -254,7 +256,7 @@ class DebugToolbar extends React.Component {
`${prefix}${i}`
));
const allNodes = nodeNames.concat(newNodeNames);
return newNodeNames.map(name => deltaAdd(
return newNodeNames.map((name) => deltaAdd(
name,
sampleArray(allNodes),
sample(SHAPES),
@@ -267,13 +269,13 @@ class DebugToolbar extends React.Component {
addNodes(n, prefix = 'zing') {
setTimeout(() => {
this.asyncDispatch(receiveNodesDelta({
add: this.createRandomNodes(n, prefix)
add: this._addNodes(n, prefix)
}));
log('added nodes', n);
}, 0);
}
randomExistingNode() {
_removeNode() {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
return [nodeNames[random(nodeNames.length - 1)]];
@@ -281,7 +283,7 @@ class DebugToolbar extends React.Component {
removeNode() {
this.asyncDispatch(receiveNodesDelta({
remove: this.randomExistingNode()
remove: this._removeNode()
}));
}
@@ -291,7 +293,7 @@ class DebugToolbar extends React.Component {
return (
<div className="debug-panel">
<div>
<strong>Add nodes </strong>
<label>Add nodes </label>
<button onClick={() => this.addNodes(1)}>+1</button>
<button onClick={() => this.addNodes(10)}>+10</button>
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
@@ -306,7 +308,7 @@ class DebugToolbar extends React.Component {
</div>
<div>
<strong>Logging </strong>
<label>Logging</label>
<button onClick={() => enableLog('*')}>scope:*</button>
<button onClick={() => enableLog('dispatcher')}>scope:dispatcher</button>
<button onClick={() => enableLog('app-key-press')}>scope:app-key-press</button>
@@ -315,7 +317,7 @@ class DebugToolbar extends React.Component {
</div>
<div>
<strong>Colors </strong>
<label>Colors</label>
<button onClick={this.toggleColors}>toggle</button>
</div>
@@ -338,7 +340,7 @@ class DebugToolbar extends React.Component {
{LABEL_PREFIXES.map(r => (
<tr key={r}>
{LABEL_PREFIXES.map(c => (
<td key={c} title={`(${r}, ${c})`} style={{backgroundColor: fn(r, c)}} />
<td key={c} title={`(${r}, ${c})`} style={{backgroundColor: fn(r, c)}}></td>
))}
</tr>
))}
@@ -347,19 +349,19 @@ class DebugToolbar extends React.Component {
))}
<div>
<strong>State </strong>
<label>state</label>
<button onClick={() => this.setLoading(true)}>Set doing initial load</button>
<button onClick={() => this.setLoading(false)}>Stop</button>
</div>
<div>
<strong>Short-lived nodes </strong>
<label>Short-lived nodes</label>
<button onClick={() => this.setShortLived()}>Toggle short-lived nodes</button>
<button onClick={() => this.setIntermittent()}>Toggle intermittent nodes</button>
</div>
<div>
<strong>Measure React perf for </strong>
<label>Measure React perf for </label>
<button onClick={() => startPerf(2)}>2s</button>
<button onClick={() => startPerf(5)}>5s</button>
<button onClick={() => startPerf(10)}>10s</button>

View File

@@ -3,11 +3,8 @@ import { connect } from 'react-redux';
import NodeDetails from './node-details';
import EmbeddedTerminal from './embedded-terminal';
import {
DETAILS_PANEL_WIDTH as WIDTH,
DETAILS_PANEL_OFFSET as OFFSET,
DETAILS_PANEL_MARGINS as MARGINS
} from '../constants/styles';
import { DETAILS_PANEL_WIDTH as WIDTH, DETAILS_PANEL_OFFSET as OFFSET,
DETAILS_PANEL_MARGINS as MARGINS } from '../constants/styles';
class DetailsCard extends React.Component {
@@ -33,15 +30,15 @@ class DetailsCard extends React.Component {
const scaleY = origin.height / (window.innerHeight - MARGINS.bottom - MARGINS.top) / 2;
const scaleX = origin.width / WIDTH / 2;
const centerX = window.innerWidth - MARGINS.right - (WIDTH / 2);
const centerY = (panelHeight / 2) + MARGINS.top;
const dx = (origin.left + (origin.width / 2)) - centerX;
const dy = (origin.top + (origin.height / 2)) - centerY;
const centerY = (panelHeight) / 2 + MARGINS.top;
const dx = (origin.left + origin.width / 2) - centerX;
const dy = (origin.top + origin.height / 2) - centerY;
transform = `translate(${dx}px, ${dy}px) scale(${scaleX},${scaleY})`;
} else {
// stack effect: shift top cards to the left, shrink lower cards vertically
const shiftX = -1 * this.props.index * OFFSET;
const position = this.props.cardCount - this.props.index - 1; // reverse index
const scaleY = (position === 0) ? 1 : (panelHeight - (2 * OFFSET * position)) / panelHeight;
const scaleY = position === 0 ? 1 : (panelHeight - 2 * OFFSET * position) / panelHeight;
if (scaleY !== 1) {
transform = `translateX(${shiftX}px) scaleY(${scaleY})`;
} else {

View File

@@ -9,12 +9,10 @@ class Details extends React.Component {
// render all details as cards, later cards go on top
return (
<div className="details">
{details.toIndexedSeq().map((obj, index) => (
<DetailsCard
key={obj.id} index={index} cardCount={details.size}
nodeControlStatus={controlStatus.get(obj.id)} {...obj}
/>
))}
{details.toIndexedSeq().map((obj, index) => <DetailsCard key={obj.id}
index={index} cardCount={details.size}
nodeControlStatus={controlStatus.get(obj.id)} {...obj} />
)}
</div>
);
}

View File

@@ -4,10 +4,8 @@ import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
export default createDevTools(
<DockMonitor
defaultIsVisible={false}
toggleVisibilityKey="ctrl-h"
changePositionKey="ctrl-w">
<DockMonitor defaultIsVisible={false}
toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-w">
<LogMonitor />
</DockMonitor>
);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
import Plugins from './plugins';
import Plugins from './plugins.js';
import { getUpdateBufferSize } from '../utils/update-buffer-utils';
import { contrastModeUrl, isContrastMode } from '../utils/contrast-utils';
import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate,
@@ -48,11 +48,8 @@ class Footer extends React.Component {
<div className="footer">
<div className="footer-status">
{versionUpdate && <a
className="footer-versionupdate"
title={versionUpdateTitle}
href={versionUpdate.downloadUrl}
target="_blank" rel="noopener noreferrer">
{versionUpdate && <a className="footer-versionupdate"
title={versionUpdateTitle} href={versionUpdate.downloadUrl} target="_blank">
Update available: {versionUpdate.version}
</a>}
<span className="footer-label">Version</span>
@@ -70,14 +67,11 @@ class Footer extends React.Component {
{pauseLabel !== '' && <span className="footer-label">{pauseLabel}</span>}
<span className="fa fa-pause" />
</a>
<a
className="footer-icon"
onClick={this.props.clickForceRelayout}
<a className="footer-icon" onClick={this.props.clickForceRelayout}
title={forceRelayoutTitle}>
<span className="fa fa-refresh" />
</a>
<a
className="footer-icon" onClick={this.props.clickDownloadGraph}
<a className="footer-icon" onClick={this.props.clickDownloadGraph}
title="Save canvas as SVG (does not include search highlighting)">
<span className="fa fa-download" />
</a>
@@ -87,13 +81,11 @@ class Footer extends React.Component {
<a className="footer-icon" href={otherContrastModeUrl} title={otherContrastModeTitle}>
<span className="fa fa-adjust" />
</a>
<a
className="footer-icon" title="Report an issue"
href="https://gitreports.com/issue/weaveworks/scope"
target="_blank" rel="noopener noreferrer">
<a className="footer-icon" href="https://gitreports.com/issue/weaveworks/scope" target="_blank" title="Report an issue">
<span className="fa fa-bug" />
</a>
<a className="footer-icon" onClick={this.props.toggleHelp} title="Show help">
<a className="footer-icon" onClick={this.props.toggleHelp}
title="Show help">
<span className="fa fa-question" />
</a>
</div>
@@ -114,11 +106,6 @@ function mapStateToProps(state) {
export default connect(
mapStateToProps,
{
clickDownloadGraph,
clickForceRelayout,
clickPauseUpdate,
clickResumeUpdate,
toggleHelp
}
{ clickDownloadGraph, clickForceRelayout, clickPauseUpdate,
clickResumeUpdate, toggleHelp }
)(Footer);

View File

@@ -4,21 +4,6 @@ import classNames from 'classnames';
import { toggleGridMode } from '../actions/app-actions';
const Item = (icons, label, isSelected, onClick) => {
const className = classNames('grid-mode-selector-action', {
'grid-mode-selector-action-selected': isSelected
});
return (
<div
className={className}
onClick={onClick} >
<span className={icons} style={{fontSize: 12}} />
<span>{label}</span>
</div>
);
};
class GridModeSelector extends React.Component {
constructor(props, context) {
@@ -36,14 +21,28 @@ class GridModeSelector extends React.Component {
return this.props.toggleGridMode(false);
}
renderItem(icons, label, isSelected, onClick) {
const className = classNames('grid-mode-selector-action', {
'grid-mode-selector-action-selected': isSelected
});
return (
<div
className={className}
onClick={onClick} >
<span className={icons} style={{fontSize: 12}} />
<span>{label}</span>
</div>
);
}
render() {
const { gridMode } = this.props;
return (
<div className="grid-mode-selector">
<div className="grid-mode-selector-wrapper">
{Item('fa fa-share-alt', 'Graph', !gridMode, this.disableGridMode)}
{Item('fa fa-table', 'Table', gridMode, this.enableGridMode)}
{this.renderItem('fa fa-share-alt', 'Graph', !gridMode, this.disableGridMode)}
{this.renderItem('fa fa-table', 'Table', gridMode, this.enableGridMode)}
</div>
</div>
);

View File

@@ -85,7 +85,7 @@ function renderSearches(searches) {
{searches.map(({term, label}) => (
<div key={term} className="help-panel-search-row">
<div className="help-panel-search-row-term">
<i className="fa fa-search search-label-icon" />
<i className="fa fa-search search-label-icon"></i>
{term}
</div>
<div className="help-panel-search-row-term-label">{label}</div>

View File

@@ -1,4 +1,3 @@
/* eslint react/jsx-first-prop-new-line: "off" */
/* eslint max-len: "off" */
import React from 'react';

View File

@@ -6,22 +6,25 @@ import MatchedText from './matched-text';
const SHOW_ROW_COUNT = 2;
const MAX_MATCH_LENGTH = 24;
const Match = match => (
<div className="matched-results-match" key={match.label}>
<div className="matched-results-match-wrapper">
<span className="matched-results-match-label">
{match.label}:
</span>
<MatchedText
text={match.text} match={match}
maxLength={MAX_MATCH_LENGTH}
truncate={match.truncate} />
</div>
</div>
);
class MatchedResults extends React.Component {
renderMatch(matches, field) {
const match = matches.get(field);
const text = match.text;
return (
<div className="matched-results-match" key={match.label}>
<div className="matched-results-match-wrapper">
<span className="matched-results-match-label">
{match.label}:
</span>
<MatchedText text={text} match={match} maxLength={MAX_MATCH_LENGTH}
truncate={match.truncate} />
</div>
</div>
);
}
render() {
const { matches, style } = this.props;
@@ -41,7 +44,7 @@ class MatchedResults extends React.Component {
return (
<div className="matched-results" style={style}>
{matches.keySeq().take(SHOW_ROW_COUNT).map(fieldId => Match(matches.get(fieldId)))}
{matches.keySeq().take(SHOW_ROW_COUNT).map(fieldId => this.renderMatch(matches, fieldId))}
{moreFieldMatches && <div className="matched-results-more" title={moreFieldMatchesTitle}>
{`${moreFieldMatches.size} more matches`}
</div>}

View File

@@ -84,7 +84,7 @@ class MatchedText extends React.Component {
render() {
const { match, text, truncate, maxLength } = this.props;
const showFullValue = !truncate || (match && (match.start + match.length) > truncate);
const showFullValue = !truncate || match && match.start + match.length > truncate;
const displayText = showFullValue ? text : text.slice(0, truncate);
if (!match) {

View File

@@ -45,7 +45,7 @@ class MetricSelectorItem extends React.Component {
onMouseOver={this.onMouseOver}
onClick={this.onMouseClick}>
{metric.get('label')}
{isPinned && <span className="fa fa-thumb-tack" />}
{isPinned && <span className="fa fa-thumb-tack"></span>}
</div>
);
}

View File

@@ -50,7 +50,7 @@ class NetworkSelectorItem extends React.Component {
onClick={this.onMouseClick}
style={style}>
{network.get('label')}
{isPinned && <span className="fa fa-thumb-tack" />}
{isPinned && <span className="fa fa-thumb-tack"></span>}
</div>
);
}

View File

@@ -20,7 +20,7 @@ function getTruncationText(count) {
+ ` (${count} extra entries not included). We are working to remove this limitation.`;
}
class NodeDetails extends React.Component {
export class NodeDetails extends React.Component {
constructor(props, context) {
super(props, context);
@@ -53,10 +53,8 @@ class NodeDetails extends React.Component {
return (
<div className="node-details-tools-wrapper">
<div className="node-details-tools">
{showSwitchTopology && <span
title={topologyTitle}
className="fa fa-long-arrow-left"
onClick={this.handleShowTopologyForNode}>
{showSwitchTopology && <span title={topologyTitle}
className="fa fa-long-arrow-left" onClick={this.handleShowTopologyForNode}>
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
</span>}
<span title="Close details" className="fa fa-close" onClick={this.handleClickClose} />
@@ -170,8 +168,7 @@ class NodeDetails extends React.Component {
</div>
{showControls && <div className="node-details-controls-wrapper" style={styles.controls}>
<NodeDetailsControls
nodeId={this.props.nodeId}
<NodeDetailsControls nodeId={this.props.nodeId}
controls={details.controls}
pending={pending}
error={error} />
@@ -187,23 +184,20 @@ class NodeDetails extends React.Component {
<NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
</div>}
{details.connections && details.connections.map(connections => (
<div className="node-details-content-section" key={connections.id}>
<NodeDetailsTable
{...connections}
nodes={connections.connections}
nodeIdKey="nodeId"
/>
{details.connections && details.connections.map(connections => <div
className="node-details-content-section" key={connections.id}>
<NodeDetailsTable {...connections} nodes={connections.connections}
nodeIdKey="nodeId" />
</div>
))}
)}
{details.children && details.children.map(children => (
<div className="node-details-content-section" key={children.topologyId}>
{details.children && details.children.map(children => <div
className="node-details-content-section" key={children.topologyId}>
<NodeDetailsTable {...children} />
</div>
))}
)}
{details.tables && details.tables.length > 0 && details.tables.map((table) => {
{details.tables && details.tables.length > 0 && details.tables.map(table => {
if (table.rows.length > 0) {
return (
<div className="node-details-content-section" key={table.id}>
@@ -214,8 +208,7 @@ class NodeDetails extends React.Component {
<Warning text={getTruncationText(table.truncationCount)} />
</span>}
</div>
<NodeDetailsLabels
rows={table.rows} controls={table.controls}
<NodeDetailsLabels rows={table.rows} controls={table.controls}
matches={nodeMatches.get('tables')} />
</div>
);

View File

@@ -21,7 +21,7 @@ export default function NodeDetailsControls({controls, error, nodeId, pending})
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={nodeId} control={control} pending={pending} key={control.id} />)}
</span>
{controls && <span title="Applying..." className={spinnerClassName} />}
{controls && <span title="Applying..." className={spinnerClassName}></span>}
</div>
);
}

View File

@@ -6,10 +6,9 @@ import { formatMetric } from '../../utils/string-utils';
function NodeDetailsHealthItem(props) {
return (
<div className="node-details-health-item">
<div className="node-details-health-item-value">{formatMetric(props.value, props)}</div>
<div className="node-details-health-item-value">{formatMetric(props.value, props)}</div>
<div className="node-details-health-item-sparkline">
<Sparkline
data={props.samples} max={props.max} format={props.format}
<Sparkline data={props.samples} max={props.max} format={props.format}
first={props.first} last={props.last} />
</div>
<div className="node-details-health-item-label">{props.label}</div>

View File

@@ -33,13 +33,10 @@ export default class NodeDetailsHealth extends React.Component {
<div className="node-details-health" style={{flexWrap, justifyContent}}>
<div className="node-details-health-wrapper">
{primeMetrics.map(item => <NodeDetailsHealthItem key={item.id} {...item} />)}
{showOverflow && <NodeDetailsHealthOverflow
items={overflowMetrics}
handleClick={this.handleClickMore}
/>}
{showOverflow && <NodeDetailsHealthOverflow items={overflowMetrics}
handleClick={this.handleClickMore} />}
</div>
<ShowMore
handleClick={this.handleClickMore} collection={this.props.metrics}
<ShowMore handleClick={this.handleClickMore} collection={this.props.metrics}
expanded={this.state.expanded} notShown={notShown} hideNumber />
</div>
);

View File

@@ -38,7 +38,7 @@ export default class NodeDetailsInfo extends React.Component {
return (
<div className="node-details-info">
{rows.map((field) => {
{rows.map(field => {
const { value, title } = formatDataType(field);
return (
<div className="node-details-info-field" key={field.id}>
@@ -54,8 +54,7 @@ export default class NodeDetailsInfo extends React.Component {
</div>
);
})}
<ShowMore
handleClick={this.handleClickMore} collection={this.props.rows}
<ShowMore handleClick={this.handleClickMore} collection={this.props.rows}
expanded={this.state.expanded} notShown={notShown} />
</div>
);

View File

@@ -6,14 +6,6 @@ import MatchedText from '../matched-text';
import NodeDetailsControlButton from './node-details-control-button';
import ShowMore from '../show-more';
const Controls = controls => (
<div className="node-details-labels-controls">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={control.nodeId} control={control} key={control.id} />)}
</div>
);
export default class NodeDetailsLabels extends React.Component {
constructor(props, context) {
@@ -23,6 +15,7 @@ export default class NodeDetailsLabels extends React.Component {
limit: this.DEFAULT_LIMIT,
};
this.handleLimitClick = this.handleLimitClick.bind(this);
this.renderControls = this.renderControls.bind(this);
}
handleLimitClick() {
@@ -30,6 +23,15 @@ export default class NodeDetailsLabels extends React.Component {
this.setState({limit});
}
renderControls(controls) {
return (
<div className="node-details-labels-controls">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={control.nodeId} control={control} key={control.id} />)}
</div>
);
}
render() {
const { controls, matches = makeMap() } = this.props;
let rows = this.props.rows;
@@ -47,12 +49,10 @@ export default class NodeDetailsLabels extends React.Component {
return (
<div className="node-details-labels">
{controls && Controls(controls)}
{rows.map(field => (
<div className="node-details-labels-field" key={field.id}>
<div
className="node-details-labels-field-label truncate"
title={field.label} key={field.id}>
{controls && this.renderControls(controls)}
{rows.map(field => (<div className="node-details-labels-field" key={field.id}>
<div className="node-details-labels-field-label truncate" title={field.label}
key={field.id}>
{field.label}
</div>
<div className="node-details-labels-field-value truncate" title={field.value}>
@@ -60,8 +60,7 @@ export default class NodeDetailsLabels extends React.Component {
</div>
</div>
))}
<ShowMore
handleClick={this.handleLimitClick} collection={this.props.rows}
<ShowMore handleClick={this.handleLimitClick} collection={this.props.rows}
expanded={expanded} notShown={notShown} />
</div>
);

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { clickRelative } from '../../actions/app-actions';
@@ -9,29 +10,18 @@ class NodeDetailsRelativesLink extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
this.saveNodeRef = this.saveNodeRef.bind(this);
}
handleClick(ev) {
ev.preventDefault();
this.props.dispatch(clickRelative(
this.props.id,
this.props.topologyId,
this.props.label,
this.node.getBoundingClientRect()
));
}
saveNodeRef(ref) {
this.node = ref;
this.props.dispatch(clickRelative(this.props.id, this.props.topologyId,
this.props.label, ReactDOM.findDOMNode(this).getBoundingClientRect()));
}
render() {
const title = `View in ${this.props.topologyId}: ${this.props.label}`;
return (
<span
className="node-details-relatives-link" title={title}
onClick={this.handleClick} ref={this.saveNodeRef}>
<span className="node-details-relatives-link" title={title} onClick={this.handleClick}>
<MatchedText text={this.props.label} match={this.props.match} />
</span>
);

View File

@@ -39,11 +39,8 @@ export default class NodeDetailsRelatives extends React.Component {
key={relative.id}
match={matches.get(relative.id)}
{...relative} />))}
{showLimitAction && <span
className="node-details-relatives-more"
onClick={this.handleLimitClick}>
{limitActionText}
</span>}
{showLimitAction && <span className="node-details-relatives-more"
onClick={this.handleLimitClick}>{limitActionText}</span>}
</div>
);
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { clickRelative } from '../../actions/app-actions';
@@ -8,21 +9,12 @@ class NodeDetailsTableNodeLink extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
this.saveNodeRef = this.saveNodeRef.bind(this);
}
handleClick(ev) {
ev.preventDefault();
this.props.dispatch(clickRelative(
this.props.nodeId,
this.props.topologyId,
this.props.label,
this.node.getBoundingClientRect()
));
}
saveNodeRef(ref) {
this.node = ref;
this.props.dispatch(clickRelative(this.props.nodeId, this.props.topologyId,
this.props.label, ReactDOM.findDOMNode(this).getBoundingClientRect()));
}
render() {
@@ -31,9 +23,8 @@ class NodeDetailsTableNodeLink extends React.Component {
if (linkable) {
return (
<span
className="node-details-table-node-link" title={title}
ref={this.saveNodeRef} onClick={this.handleClick}>
<span className="node-details-table-node-link" title={title}
onClick={this.handleClick}>
{label}
</span>
);

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import NodeDetailsTableNodeLink from './node-details-table-node-link';
@@ -7,9 +8,9 @@ import { formatDataType } from '../../utils/string-utils';
function getValuesForNode(node) {
const values = {};
['metrics', 'metadata'].forEach((collection) => {
['metrics', 'metadata'].forEach(collection => {
if (node[collection]) {
node[collection].forEach((field) => {
node[collection].forEach(field => {
const result = Object.assign({}, field);
result.valueType = collection;
values[field.id] = result;
@@ -17,7 +18,7 @@ function getValuesForNode(node) {
}
});
(node.parents || []).forEach((p) => {
(node.parents || []).forEach(p => {
values[p.topologyId] = {
id: p.topologyId,
label: p.topologyId,
@@ -39,9 +40,7 @@ function renderValues(node, columns = [], columnStyles = []) {
if (field.valueType === 'metadata') {
const {value, title} = formatDataType(field);
return (
<td
className="node-details-table-node-value truncate"
title={title}
<td className="node-details-table-node-value truncate" title={title}
style={style}
key={field.id}>
{value}
@@ -50,9 +49,7 @@ function renderValues(node, columns = [], columnStyles = []) {
}
if (field.valueType === 'relatives') {
return (
<td
className="node-details-table-node-value truncate"
title={field.value}
<td className="node-details-table-node-value truncate" title={field.value}
style={style}
key={field.id}>
{<NodeDetailsTableNodeLink linkable nodeId={field.relative.id} {...field.relative} />}
@@ -78,15 +75,15 @@ export default class NodeDetailsTableRow extends React.Component {
//
this.mouseDragOrigin = [0, 0];
this.saveLabelElementRef = this.saveLabelElementRef.bind(this);
this.storeLabelRef = this.storeLabelRef.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}
saveLabelElementRef(ref) {
this.labelElement = ref;
storeLabelRef(ref) {
this.labelEl = ref;
}
onMouseEnter() {
@@ -117,7 +114,7 @@ export default class NodeDetailsTableRow extends React.Component {
}
const { node, onClick } = this.props;
onClick(ev, node, this.labelElement);
onClick(ev, node, ReactDOM.findDOMNode(this.labelEl));
}
render() {
@@ -135,9 +132,8 @@ export default class NodeDetailsTableRow extends React.Component {
onMouseEnter={onMouseEnterRow && this.onMouseEnter}
onMouseLeave={onMouseLeaveRow && this.onMouseLeave}
className={className}>
<td
className="node-details-table-node-label truncate"
ref={this.saveLabelElementRef} style={firstColumnStyle}>
<td ref={this.storeLabelRef} className="node-details-table-node-label truncate"
style={firstColumnStyle}>
{this.props.renderIdCell(Object.assign(node, {topologyId, nodeId}))}
</td>
{values}
@@ -148,5 +144,5 @@ export default class NodeDetailsTableRow extends React.Component {
NodeDetailsTableRow.defaultProps = {
renderIdCell: props => <NodeDetailsTableNodeLink {...props} />
renderIdCell: (props) => <NodeDetailsTableNodeLink {...props} />
};

View File

@@ -113,13 +113,13 @@ function getNodeValue(node, header) {
function getValueForSortedBy(sortedByHeader) {
return node => maybeToLower(getNodeValue(node, sortedByHeader));
return (node) => maybeToLower(getNodeValue(node, sortedByHeader));
}
function getMetaDataSorters(nodes) {
// returns an array of sorters that will take a node
return get(nodes, [0, 'metadata'], []).map((field, index) => (node) => {
return get(nodes, [0, 'metadata'], []).map((field, index) => node => {
const nodeMetadataField = node.metadata && node.metadata[index];
if (nodeMetadataField) {
if (isNumber(nodeMetadataField)) {
@@ -225,7 +225,7 @@ export default class NodeDetailsTable extends React.Component {
<tr>
{headers.map((header, i) => {
const headerClasses = ['node-details-table-header', 'truncate'];
const onHeaderClick = (ev) => {
const onHeaderClick = ev => {
this.handleHeaderClick(ev, header.id, sortedBy, sortedDesc);
};
// sort by first metric by default
@@ -243,8 +243,7 @@ export default class NodeDetailsTable extends React.Component {
header.label;
return (
<td
className={headerClasses.join(' ')} style={style} onClick={onHeaderClick}
<td className={headerClasses.join(' ')} style={style} onClick={onHeaderClick}
title={header.label} key={header.id}>
{isSortedAsc
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
@@ -279,15 +278,14 @@ export default class NodeDetailsTable extends React.Component {
const className = classNames('node-details-table-wrapper-wrapper', this.props.className);
return (
<div className={className} style={this.props.style}>
<div className={className}
style={this.props.style}>
<div className="node-details-table-wrapper">
<table className="node-details-table">
<thead>
{this.renderHeaders(sortedBy, sortedDesc)}
</thead>
<tbody
style={this.props.tbodyStyle}
onMouseEnter={onMouseEnter}
<tbody style={this.props.tbodyStyle} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}>
{nodes && nodes.map(node => (
<NodeDetailsTableRow

View File

@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import NodesChart from '../charts/nodes-chart';
import NodesGrid from '../charts/nodes-grid';
import NodesError from '../charts/nodes-error';
import DelayedShow from '../utils/delayed-show';
import { DelayedShow } from '../utils/delayed-show';
import { Loading, getNodeType } from './loading';
import { isTopologyEmpty } from '../utils/topology-utils';
import { CANVAS_MARGINS } from '../constants/styles';
@@ -13,20 +13,6 @@ const navbarHeight = 194;
const marginTop = 0;
const EmptyTopologyError = show => (
<NodesError faIconClass="fa-circle-thin" hidden={!show}>
<div className="heading">Nothing to show. This can have any of these reasons:</div>
<ul>
<li>We haven&apos;t received any reports from probes recently.
Are the probes properly configured?</li>
<li>There are nodes, but they&apos;re currently hidden. Check the view options
in the bottom-left if they allow for showing hidden nodes.</li>
<li>Containers view only: you&apos;re not running Docker,
or you don&apos;t have any containers.</li>
</ul>
</NodesError>
);
class Nodes extends React.Component {
constructor(props, context) {
super(props, context);
@@ -46,6 +32,22 @@ class Nodes extends React.Component {
window.removeEventListener('resize', this.handleResize);
}
renderEmptyTopologyError(show) {
return (
<NodesError faIconClass="fa-circle-thin" hidden={!show}>
<div className="heading">Nothing to show. This can have any of these reasons:</div>
<ul>
<li>We haven't received any reports from probes recently.
Are the probes properly configured?</li>
<li>There are nodes, but they're currently hidden. Check the view options
in the bottom-left if they allow for showing hidden nodes.</li>
<li>Containers view only: you're not running Docker,
or you don't have any containers.</li>
</ul>
</NodesError>
);
}
render() {
const { topologyEmpty, gridMode, topologiesLoaded, nodesLoaded, topologies,
currentTopology } = this.props;
@@ -58,11 +60,16 @@ class Nodes extends React.Component {
itemType={getNodeType(currentTopology, topologies)}
show={topologiesLoaded && !nodesLoaded} />
</DelayedShow>
{EmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)}
{this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)}
{gridMode ?
<NodesGrid {...this.state} nodeSize="24" margins={CANVAS_MARGINS} /> :
<NodesChart {...this.state} margins={CANVAS_MARGINS} />}
<NodesGrid {...this.state}
nodeSize="24"
margins={CANVAS_MARGINS}
/> :
<NodesChart {...this.state}
margins={CANVAS_MARGINS}
/>}
</div>
);
}

View File

@@ -3,25 +3,24 @@ import { connect } from 'react-redux';
import classNames from 'classnames';
import ReactTooltip from 'react-tooltip';
const Plugin = ({id, label, description, status}) => {
const error = status !== 'ok';
const className = classNames({ error });
const title = `Plugin description: ${description}<br />Status: ${status}`;
// Inner span to hold styling so we don't effect the "before:content"
return (
<span className="plugins-plugin" key={id}>
<span className={className} data-tip={title} data-multiline>
{error && <span className="plugins-plugin-icon fa fa-exclamation-circle" />}
{label || id}
</span>
<ReactTooltip class="tooltip" effect="solid" offset={{right: 7}} />
</span>
);
};
class Plugins extends React.Component {
renderPlugin({id, label, description, status}) {
const error = status !== 'ok';
const className = classNames({ error });
const title = `Plugin description: ${description}<br />Status: ${status}`;
// Inner span to hold styling so we don't effect the "before:content"
return (
<span className="plugins-plugin" key={id}>
<span className={className} data-tip={title} data-multiline>
{error && <span className="plugins-plugin-icon fa fa-exclamation-circle" />}
{label || id}
</span>
<ReactTooltip class="tooltip" effect="solid" offset={{right: 7}} />
</span>
);
}
render() {
const hasPlugins = this.props.plugins && this.props.plugins.size > 0;
return (
@@ -29,7 +28,8 @@ class Plugins extends React.Component {
<span className="plugins-label">
Plugins:
</span>
{hasPlugins && this.props.plugins.toIndexedSeq().map(plugin => Plugin(plugin.toJS()))}
{hasPlugins && this.props.plugins.toIndexedSeq()
.map(plugin => this.renderPlugin(plugin.toJS()))}
{!hasPlugins && <span className="plugins-empty">n/a</span>}
</div>
);

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { debounce } from 'lodash';
@@ -47,7 +48,6 @@ class Search extends React.Component {
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.saveQueryInputRef = this.saveQueryInputRef.bind(this);
this.doSearch = debounce(this.doSearch.bind(this), 200);
this.state = {
value: ''
@@ -81,10 +81,6 @@ class Search extends React.Component {
this.props.doSearch(value);
}
saveQueryInputRef(ref) {
this.queryInput = ref;
}
componentWillReceiveProps(nextProps) {
// when cleared from the outside, reset internal state
if (this.props.searchQuery !== nextProps.searchQuery && nextProps.searchQuery === '') {
@@ -94,9 +90,9 @@ class Search extends React.Component {
componentDidUpdate() {
if (this.props.searchFocused) {
this.queryInput.focus();
ReactDOM.findDOMNode(this.refs.queryInput).focus();
} else if (!this.state.value) {
this.queryInput.blur();
ReactDOM.findDOMNode(this.refs.queryInput).blur();
}
}
@@ -125,14 +121,13 @@ class Search extends React.Component {
<div className="search-input">
{showPinnedSearches && pinnedSearches.toIndexedSeq()
.map(query => <SearchItem query={query} key={query} />)}
<input
className="search-input-field" type="text" id={inputId}
<input className="search-input-field" type="text" id={inputId}
value={value} onChange={this.handleChange}
onFocus={this.handleFocus} onBlur={this.handleBlur}
disabled={disabled} ref={this.saveQueryInputRef} />
disabled={disabled} ref="queryInput" />
</div>
<div className="search-label">
<i className="fa fa-search search-label-icon" />
<i className="fa fa-search search-label-icon"></i>
<label className="search-label-hint" htmlFor={inputId}>
Search
</label>

View File

@@ -81,14 +81,10 @@ export default class Sparkline extends React.Component {
return (
<div title={title}>
<svg width={this.props.width} height={this.props.height}>
<path
className="sparkline" fill="none" stroke={this.props.strokeColor}
strokeWidth={this.props.strokeWidth} d={this.line(data)}
/>
<circle
className="sparkcircle" cx={lastX} cy={lastY} fill="#46466a"
fillOpacity="0.6" stroke="none" r={this.props.circleDiameter}
/>
<path className="sparkline" fill="none" stroke={this.props.strokeColor}
strokeWidth={this.props.strokeWidth} ref="path" d={this.line(data)} />
<circle className="sparkcircle" cx={lastX} cy={lastY} fill="#46466a"
fillOpacity="0.6" stroke="none" r={this.props.circleDiameter} />
</svg>
</div>
);
@@ -97,7 +93,7 @@ export default class Sparkline extends React.Component {
}
Sparkline.propTypes = {
data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
data: React.PropTypes.array.isRequired
};
Sparkline.defaultProps = {

View File

@@ -1,15 +1,16 @@
/* eslint no-return-assign: "off", react/jsx-no-bind: "off" */
import debug from 'debug';
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { debounce } from 'lodash';
import Term from 'xterm';
import { clickCloseTerminal } from '../actions/app-actions';
import { getNeutralColor } from '../utils/color-utils';
import { setDocumentTitle } from '../utils/title-utils';
import { getPipeStatus, basePath, doResizeTty } from '../utils/web-api-utils';
import Term from 'xterm';
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`;
@@ -72,7 +73,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);
@@ -96,8 +97,6 @@ class Terminal extends React.Component {
this.handleCloseClick = this.handleCloseClick.bind(this);
this.handlePopoutTerminal = this.handlePopoutTerminal.bind(this);
this.saveInnerFlexRef = this.saveInnerFlexRef.bind(this);
this.saveNodeRef = this.saveNodeRef.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleResizeDebounced = debounce(this.handleResize, 500);
}
@@ -124,7 +123,7 @@ class Terminal extends React.Component {
}
this.socket = null;
const wereConnected = this.state.connected;
if (this.isComponentMounted) {
if (this._isMounted) {
// Calling setState on an unmounted component will throw a warning.
// `connected` will get set to false by `componentWillUnmount`.
this.setState({connected: false});
@@ -177,13 +176,14 @@ class Terminal extends React.Component {
}
componentDidMount() {
this.isComponentMounted = true;
this._isMounted = true;
if (this.props.connect) {
this.mountTerminal();
}
}
mountTerminal() {
const component = this;
this.term = new Term({
cols: this.state.cols,
rows: this.state.rows,
@@ -192,7 +192,8 @@ class Terminal extends React.Component {
scrollback: 10000,
});
this.term.open(this.innerFlex);
const innerNode = ReactDOM.findDOMNode(component.innerFlex);
this.term.open(innerNode);
this.term.on('data', (data) => {
this.scrollToBottom();
if (this.socket) {
@@ -216,7 +217,7 @@ class Terminal extends React.Component {
}
componentWillUnmount() {
this.isComponentMounted = false;
this._isMounted = false;
this.setState({connected: false});
log('cwu terminal');
@@ -262,15 +263,16 @@ class Terminal extends React.Component {
const paramString = JSON.stringify(this.props);
this.props.dispatch(clickCloseTerminal(this.getPipeId()));
const bcr = this.node.getBoundingClientRect();
const minWidth = (this.state.characterWidth * 80) + (8 * 2);
const bcr = ReactDOM.findDOMNode(this).getBoundingClientRect();
const minWidth = this.state.characterWidth * 80 + (8 * 2);
openNewWindow(`terminal.html#!/state/${paramString}`, bcr, minWidth);
}
handleResize() {
const innerNode = ReactDOM.findDOMNode(this.innerFlex);
// scrollbar === 16px
const width = this.innerFlex.clientWidth - (2 * 8) - 16;
const height = this.innerFlex.clientHeight - (2 * 8);
const width = innerNode.clientWidth - (2 * 8) - 16;
const height = innerNode.clientHeight - (2 * 8);
const cols = Math.floor(width / this.state.characterWidth);
const rows = Math.floor(height / this.state.characterHeight);
@@ -311,8 +313,7 @@ class Terminal extends React.Component {
onClick={this.handlePopoutTerminal}>
Pop out
</span>
<span
title="Close" className="terminal-header-tools-item-icon fa fa-close"
<span title="Close" className="terminal-header-tools-item-icon fa fa-close"
onClick={this.handleCloseClick} />
</div>
<span className="terminal-header-title">{this.getTitle()}</span>
@@ -356,14 +357,6 @@ class Terminal extends React.Component {
);
}
saveNodeRef(ref) {
this.node = ref;
}
saveInnerFlexRef(ref) {
this.innerFlex = ref;
}
render() {
const innerFlexStyle = {
opacity: this.state.connected ? 1 : 0.8,
@@ -377,10 +370,13 @@ class Terminal extends React.Component {
});
return (
<div className="terminal-wrapper" ref={this.saveNodeRef}>
<div className="terminal-wrapper">
{this.isEmbedded() && this.getTerminalHeader()}
<div className={innerClassName} style={innerFlexStyle} ref={this.saveInnerFlexRef}>
<div style={innerStyle} />
<div
ref={(ref) => this.innerFlex = ref}
className={innerClassName}
style={innerFlexStyle} >
<div style={innerStyle} ref={(ref) => this.inner = ref} />
</div>
{this.getTerminalStatusBar()}
</div>

View File

@@ -4,18 +4,6 @@ import classnames from 'classnames';
import { clickTopology } from '../actions/app-actions';
function basicTopologyInfo(topology, searchMatchCount) {
const info = [
`Nodes: ${topology.getIn(['stats', 'node_count'])}`,
`Connections: ${topology.getIn(['stats', 'edge_count'])}`
];
if (searchMatchCount) {
info.push(`Search Matches: ${searchMatchCount}`);
}
return info.join('\n');
}
class Topologies extends React.Component {
constructor(props, context) {
@@ -33,15 +21,14 @@ class Topologies extends React.Component {
const topologyId = subTopology.get('id');
const searchMatches = this.props.searchNodeMatches.get(subTopology.get('id'));
const searchMatchCount = searchMatches ? searchMatches.size : 0;
const title = basicTopologyInfo(subTopology, searchMatchCount);
const title = this.renderTitle(subTopology, searchMatchCount);
const className = classnames('topologies-sub-item', {
'topologies-sub-item-active': isActive,
'topologies-sub-item-matched': searchMatchCount
});
return (
<div
className={className} title={title} key={topologyId} rel={topologyId}
<div className={className} title={title} key={topologyId} rel={topologyId}
onClick={this.onTopologyClick}>
<div className="topologies-sub-item-label">
{subTopology.get('name')}
@@ -50,6 +37,15 @@ class Topologies extends React.Component {
);
}
renderTitle(topology, searchMatchCount) {
let title = `Nodes: ${topology.getIn(['stats', 'node_count'])}\n`
+ `Connections: ${topology.getIn(['stats', 'node_count'])}`;
if (searchMatchCount) {
title = `${title}\nSearch Matches: ${searchMatchCount}`;
}
return title;
}
renderTopology(topology) {
const isActive = topology === this.props.currentTopology;
const searchMatches = this.props.searchNodeMatches.get(topology.get('id'));
@@ -59,7 +55,7 @@ class Topologies extends React.Component {
'topologies-item-main-matched': searchMatchCount
});
const topologyId = topology.get('id');
const title = basicTopologyInfo(topology, searchMatchCount);
const title = this.renderTitle(topology, searchMatchCount);
return (
<div className="topologies-item" key={topologyId}>

View File

@@ -1,11 +1,11 @@
require('font-awesome-webpack');
require('../styles/contrast.less');
require('../images/favicon.ico');
import 'babel-polyfill';
import 'font-awesome-webpack';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import '../styles/main.less';
import '../images/favicon.ico';
import configureStore from './stores/configureStore';
const store = configureStore();

View File

@@ -149,13 +149,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,
value: lastValue,
samples,
max
};
const slidingWindow = {first: movingFirstDate,
last: movingLastDate, max, samples, value: lastValue};
return <ComposedComponent {...this.props} {...slidingWindow} />;
}

View File

@@ -1,17 +1,18 @@
require('font-awesome-webpack');
require('../styles/main.less');
require('../images/favicon.ico');
import 'babel-polyfill';
import 'font-awesome-webpack';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './stores/configureStore.dev';
import DevTools from './components/dev-tools';
import Immutable from 'immutable';
import installDevTools from 'immutable-devtools';
import '../styles/main.less';
import '../images/favicon.ico';
import configureStore from './stores/configureStore.dev';
import DevTools from './components/dev-tools';
installDevTools(Immutable);
const store = configureStore();
function renderApp() {

View File

@@ -1,11 +1,11 @@
require('font-awesome-webpack');
require('../styles/main.less');
require('../images/favicon.ico');
import 'babel-polyfill';
import 'font-awesome-webpack';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import '../styles/main.less';
import '../images/favicon.ico';
import configureStore from './stores/configureStore';
const store = configureStore();

View File

@@ -95,7 +95,7 @@ function setTopology(state, topologyId) {
}
function setDefaultTopologyOptions(state, topologyList) {
topologyList.forEach((topology) => {
topologyList.forEach(topology => {
let defaultOptions = makeOrderedMap();
if (topology.has('options') && topology.get('options')) {
topology.get('options').forEach((option) => {
@@ -230,7 +230,7 @@ export function rootReducer(state = initialState, action) {
}
case ActionTypes.CLICK_PAUSE_UPDATE: {
return state.set('updatePausedAt', new Date());
return state.set('updatePausedAt', new Date);
}
case ActionTypes.CLICK_RELATIVE: {
@@ -371,13 +371,13 @@ export function rootReducer(state = initialState, action) {
case ActionTypes.ENTER_EDGE: {
// highlight adjacent nodes
state = state.update('highlightedNodeIds', (highlightedNodeIds) => {
state = state.update('highlightedNodeIds', highlightedNodeIds => {
highlightedNodeIds = highlightedNodeIds.clear();
return highlightedNodeIds.union(action.edgeId.split(EDGE_ID_SEPARATOR));
});
// highlight edge
state = state.update('highlightedEdgeIds', (highlightedEdgeIds) => {
state = state.update('highlightedEdgeIds', highlightedEdgeIds => {
highlightedEdgeIds = highlightedEdgeIds.clear();
return highlightedEdgeIds.add(action.edgeId);
});
@@ -392,18 +392,18 @@ export function rootReducer(state = initialState, action) {
state = state.set('mouseOverNodeId', nodeId);
// highlight adjacent nodes
state = state.update('highlightedNodeIds', (highlightedNodeIds) => {
state = state.update('highlightedNodeIds', highlightedNodeIds => {
highlightedNodeIds = highlightedNodeIds.clear();
highlightedNodeIds = highlightedNodeIds.add(nodeId);
return highlightedNodeIds.union(adjacentNodes);
});
// highlight edge
state = state.update('highlightedEdgeIds', (highlightedEdgeIds) => {
state = state.update('highlightedEdgeIds', highlightedEdgeIds => {
highlightedEdgeIds = highlightedEdgeIds.clear();
if (adjacentNodes.size > 0) {
// all neighbour combinations because we dont know which direction exists
highlightedEdgeIds = highlightedEdgeIds.union(adjacentNodes.flatMap(adjacentId => [
highlightedEdgeIds = highlightedEdgeIds.union(adjacentNodes.flatMap((adjacentId) => [
[adjacentId, nodeId].join(EDGE_ID_SEPARATOR),
[nodeId, adjacentId].join(EDGE_ID_SEPARATOR)
]));
@@ -491,7 +491,7 @@ export function rootReducer(state = initialState, action) {
// disregard if node is not selected anymore
if (state.hasIn(['nodeDetails', action.details.id])) {
state = state.updateIn(['nodeDetails', action.details.id], (obj) => {
state = state.updateIn(['nodeDetails', action.details.id], obj => {
const result = Object.assign({}, obj);
result.notFound = false;
result.details = action.details;
@@ -554,7 +554,7 @@ export function rootReducer(state = initialState, action) {
// TODO move this setting of networks as toplevel node field to backend,
// to not rely on field IDs here. should be determined by topology implementer
state = state.update('nodes', nodes => nodes.map((node) => {
state = state.update('nodes', nodes => nodes.map(node => {
if (node.has('metadata')) {
const networks = node.get('metadata')
.find(field => field.get('id') === 'docker_container_networks');
@@ -605,7 +605,7 @@ export function rootReducer(state = initialState, action) {
case ActionTypes.RECEIVE_NOT_FOUND: {
if (state.hasIn(['nodeDetails', action.nodeId])) {
state = state.updateIn(['nodeDetails', action.nodeId], (obj) => {
state = state.updateIn(['nodeDetails', action.nodeId], obj => {
const result = Object.assign({}, obj);
result.notFound = true;
return result;

View File

@@ -1,5 +1,4 @@
import debug from 'debug';
import { identity } from 'lodash';
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
import { Map as makeMap, is, Set } from 'immutable';
@@ -45,8 +44,9 @@ function mergeDeepKeyIntersection(mapA, mapB) {
// "their results", so use the result of the wrapped selector as the argument to another selector
// here to memoize it and get what we want.
//
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, is);
const returnPreviousRefIfEqual = selector => createDeepEqualSelector(selector, identity);
const _createDeepEqualSelector = createSelectorCreator(defaultMemoize, is);
const _identity = v => v;
const returnPreviousRefIfEqual = (selector) => _createDeepEqualSelector(selector, _identity);
//
@@ -60,7 +60,7 @@ const allNodesSelector = state => state.get('nodes');
export const nodesSelector = returnPreviousRefIfEqual(
createSelector(
allNodesSelector,
allNodes => allNodes.filter(node => !node.get('filtered'))
(allNodes) => allNodes.filter(node => !node.get('filtered'))
)
);
@@ -71,7 +71,7 @@ export const adjacentNodesSelector = returnPreviousRefIfEqual(getAdjacentNodes);
export const nodeAdjacenciesSelector = returnPreviousRefIfEqual(
createSelector(
nodesSelector,
nodes => nodes.map(n => makeMap({
(nodes) => nodes.map(n => makeMap({
id: n.get('id'),
adjacency: n.get('adjacency'),
}))
@@ -81,7 +81,7 @@ export const nodeAdjacenciesSelector = returnPreviousRefIfEqual(
export const dataNodesSelector = createSelector(
nodesSelector,
nodes => nodes.map((node, id) => makeMap({
(nodes) => nodes.map((node, id) => makeMap({
id,
label: node.get('label'),
pseudo: node.get('pseudo'),

View File

@@ -1,10 +1,10 @@
require('../styles/main.less');
require('../images/favicon.ico');
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import '../styles/main.less';
import '../images/favicon.ico';
import configureStore from './stores/configureStore';
const store = configureStore();

View File

@@ -1,4 +1,4 @@
import { range } from 'lodash';
import _ from 'lodash';
describe('ArrayUtils', () => {
const ArrayUtils = require('../array-utils');
@@ -7,42 +7,34 @@ describe('ArrayUtils', () => {
const f = ArrayUtils.uniformSelect;
it('it should select the array elements uniformly, including the endpoints', () => {
{
const arr = ['x', 'y'];
expect(f(arr, 3)).toEqual(['x', 'y']);
expect(f(arr, 2)).toEqual(['x', 'y']);
}
expect(f(['x', 'y'], 3)).toEqual(['x', 'y']);
expect(f(['x', 'y'], 2)).toEqual(['x', 'y']);
{
const arr = ['A', 'B', 'C', 'D', 'E'];
expect(f(arr, 6)).toEqual(['A', 'B', 'C', 'D', 'E']);
expect(f(arr, 5)).toEqual(['A', 'B', 'C', 'D', 'E']);
expect(f(arr, 4)).toEqual(['A', 'B', 'D', 'E']);
expect(f(arr, 3)).toEqual(['A', 'C', 'E']);
expect(f(arr, 2)).toEqual(['A', 'E']);
}
expect(f(['A', 'B', 'C', 'D', 'E'], 6)).toEqual(['A', 'B', 'C', 'D', 'E']);
expect(f(['A', 'B', 'C', 'D', 'E'], 5)).toEqual(['A', 'B', 'C', 'D', 'E']);
expect(f(['A', 'B', 'C', 'D', 'E'], 4)).toEqual(['A', 'B', 'D', 'E']);
expect(f(['A', 'B', 'C', 'D', 'E'], 3)).toEqual(['A', 'C', 'E']);
expect(f(['A', 'B', 'C', 'D', 'E'], 2)).toEqual(['A', 'E']);
{
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
expect(f(arr, 12)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
expect(f(arr, 11)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
expect(f(arr, 10)).toEqual([1, 2, 3, 4, 5, 7, 8, 9, 10, 11]);
expect(f(arr, 9)).toEqual([1, 2, 3, 5, 6, 7, 9, 10, 11]);
expect(f(arr, 8)).toEqual([1, 2, 4, 5, 7, 8, 10, 11]);
expect(f(arr, 7)).toEqual([1, 2, 4, 6, 8, 10, 11]);
expect(f(arr, 6)).toEqual([1, 3, 5, 7, 9, 11]);
expect(f(arr, 5)).toEqual([1, 3, 6, 9, 11]);
expect(f(arr, 4)).toEqual([1, 4, 8, 11]);
expect(f(arr, 3)).toEqual([1, 6, 11]);
expect(f(arr, 2)).toEqual([1, 11]);
}
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12)).toEqual(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 11)).toEqual(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 10)).toEqual([1, 2, 3, 4, 5, 7, 8, 9, 10, 11]
);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 9)).toEqual([1, 2, 3, 5, 6, 7, 9, 10, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 8)).toEqual([1, 2, 4, 5, 7, 8, 10, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 7)).toEqual([1, 2, 4, 6, 8, 10, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 6)).toEqual([1, 3, 5, 7, 9, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 5)).toEqual([1, 3, 6, 9, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4)).toEqual([1, 4, 8, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 3)).toEqual([1, 6, 11]);
expect(f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 2)).toEqual([1, 11]);
{
const arr = range(1, 10001);
expect(f(arr, 4)).toEqual([1, 3334, 6667, 10000]);
expect(f(arr, 3)).toEqual([1, 5000, 10000]);
expect(f(arr, 2)).toEqual([1, 10000]);
}
expect(f(_.range(1, 10001), 4)).toEqual([1, 3334, 6667, 10000]);
expect(f(_.range(1, 10001), 3)).toEqual([1, 5000, 10000]);
expect(f(_.range(1, 10001), 2)).toEqual([1, 10000]);
});
});
});

View File

@@ -154,7 +154,7 @@ describe('SearchUtils', () => {
const fun = SearchUtils.makeRegExp;
it('should make a regexp from any string', () => {
expect(fun().source).toEqual((new RegExp()).source);
expect(fun().source).toEqual((new RegExp).source);
expect(fun('que').source).toEqual((new RegExp('que')).source);
// invalid string
expect(fun('que[').source).toEqual((new RegExp('que\\[')).source);

View File

@@ -1,11 +1,11 @@
import { range } from 'lodash';
import _ from 'lodash';
export function uniformSelect(array, size) {
if (size > array.length) {
return array;
}
return range(size).map(index =>
array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]
return _.range(size).map(index =>
array[parseInt(index * array.length / (size - 1 + 1e-9), 10)]
);
}

View File

@@ -17,7 +17,7 @@ const letterRange = endLetterRange - startLetterRange;
export function text2degree(text) {
const input = text.substr(0, 2).toUpperCase();
let num = 0;
for (let i = 0; i < input.length; i += 1) {
for (let i = 0; i < input.length; i++) {
const charCode = Math.max(Math.min(input[i].charCodeAt(), endLetterRange), startLetterRange);
num += Math.pow(letterRange, input.length - i - 1) * (charCode - startLetterRange);
}

View File

@@ -1,4 +1,4 @@
import { zipObject } from 'lodash';
import _ from 'lodash';
import { scaleLinear } from 'd3-scale';
import { extent } from 'd3-array';
@@ -104,9 +104,10 @@ function mergeMetrics(node) {
return node;
}
return Object.assign({}, node, {
metrics: (metrics[node.shape] || [])
metrics: _(metrics[node.shape])
.map((fn, name) => [name, fn(node)])
.fromPairs()
.value()
});
}
@@ -120,7 +121,7 @@ function handleAdd(nodes) {
function handleUpdated(updatedNodes, prevNodes) {
const modifiedNodesIndex = zipObject((updatedNodes || []).map(n => [n.id, n]));
const modifiedNodesIndex = _.zipObject((updatedNodes || []).map(n => [n.id, n]));
return prevNodes.toIndexedSeq().toJS().map(n => (
Object.assign({}, mergeMetrics(n), modifiedNodesIndex[n.id])
));

View File

@@ -1,7 +1,7 @@
import React from 'react';
export default class DelayedShow extends React.Component {
export class DelayedShow extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {

View File

@@ -18,7 +18,7 @@ function setInlineStyles(svg, target, emptySvgDeclarationComputed) {
function explicitlySetStyle(element, targetEl) {
const cSSStyleDeclarationComputed = getComputedStyle(element);
let computedStyleStr = '';
each(cSSStyleDeclarationComputed, (key) => {
each(cSSStyleDeclarationComputed, key => {
const value = cSSStyleDeclarationComputed.getPropertyValue(key);
if (value !== emptySvgDeclarationComputed.getPropertyValue(key) && !cssSkipValues[value]) {
computedStyleStr += `${key}:${value};`;
@@ -55,7 +55,8 @@ function setInlineStyles(svg, target, emptySvgDeclarationComputed) {
// hardcode computed css styles inside svg
const allElements = traverse(svg);
const allTargetElements = traverse(target);
for (let i = allElements.length - 1; i >= 0; i -= 1) {
let i = allElements.length;
while (i--) {
explicitlySetStyle(allElements[i], allTargetElements[i]);
}
@@ -73,11 +74,11 @@ 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');

View File

@@ -1,12 +1,12 @@
import { includes } from 'lodash';
import { scaleLog } from 'd3-scale';
import React from 'react';
import { formatMetricSvg } from './string-utils';
import { colors } from './color-utils';
import React from 'react';
export function getClipPathDefinition(clipId, size, height,
x = -size * 0.5, y = (size * 0.5) - height) {
x = -size * 0.5, y = size * 0.5 - height) {
return (
<defs>
<clipPath id={clipId}>
@@ -44,7 +44,7 @@ export function getMetricValue(metric, size) {
let displayedValue = Number(value).toFixed(1);
if (displayedValue > 0 && (!max || displayedValue < max)) {
const baseline = 0.1;
displayedValue = (valuePercentage * (1 - (baseline * 2))) + baseline;
displayedValue = valuePercentage * (1 - baseline * 2) + baseline;
} else if (displayedValue >= m.max && displayedValue > 0) {
displayedValue = 1;
}

View File

@@ -2,7 +2,7 @@ import { fromJS, List as makeList } from 'immutable';
export function getNetworkNodes(nodes) {
const networks = {};
nodes.forEach(node => (node.get('networks') || makeList()).forEach((n) => {
nodes.forEach(node => (node.get('networks') || makeList()).forEach(n => {
const networkId = n.get('id');
networks[networkId] = (networks[networkId] || []).concat([node.get('id')]);
}));

View File

@@ -132,7 +132,7 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
// metadata
if (node.get('metadata')) {
node.get('metadata').forEach((field) => {
node.get('metadata').forEach(field => {
const keyPath = [nodeId, 'metadata', field.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
query, prefix, field.get('label'), field.get('truncate'));
@@ -141,7 +141,7 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
// parents and relatives
if (node.get('parents')) {
node.get('parents').forEach((parent) => {
node.get('parents').forEach(parent => {
const keyPath = [nodeId, 'parents', parent.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, parent.get('label'),
query, prefix, parent.get('topologyId'));
@@ -153,7 +153,7 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
if (tables) {
tables.forEach((table) => {
if (table.get('rows')) {
table.get('rows').forEach((field) => {
table.get('rows').forEach(field => {
const keyPath = [nodeId, 'tables', field.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
query, prefix, field.get('label'));
@@ -164,7 +164,7 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
} else if (metric) {
const metrics = node.get('metrics');
if (metrics) {
metrics.forEach((field) => {
metrics.forEach(field => {
const keyPath = [nodeId, 'metrics', field.get('id')];
nodeMatches = findNodeMatchMetric(nodeMatches, keyPath, field.get('value'),
field.get('label'), metric, comp, value);
@@ -291,7 +291,7 @@ export function applyPinnedSearches(state) {
const pinnedSearches = state.get('pinnedSearches');
if (pinnedSearches.size > 0) {
state.get('pinnedSearches').forEach((query) => {
state.get('pinnedSearches').forEach(query => {
const parsed = parseQuery(query);
if (parsed) {
const nodeMatches = searchTopology(state.get('nodes'), parsed);

View File

@@ -3,7 +3,7 @@ import debug from 'debug';
const log = debug('scope:storage-utils');
// localStorage detection
const storage = (typeof Storage) !== 'undefined' ? window.localStorage : null;
const storage = typeof(Storage) !== 'undefined' ? window.localStorage : null;
export function storageGet(key, defaultValue) {
if (storage && storage.getItem(key) !== undefined) {

View File

@@ -19,7 +19,7 @@ export function getDefaultTopology(topologies) {
.flatMap(t => makeList([t]).concat(t.get('sub_topologies', makeList())));
return flatTopologies
.sortBy((t) => {
.sortBy(t => {
const index = TOPOLOGY_DISPLAY_PRIORITY.indexOf(t.get('id'));
return index === -1 ? Infinity : index;
})
@@ -53,7 +53,7 @@ export function buildTopologyCacheId(topologyId, topologyOptions) {
export function findTopologyById(subTree, topologyId) {
let foundTopology;
subTree.forEach((topology) => {
subTree.forEach(topology => {
if (endsWith(topology.get('url'), topologyId)) {
foundTopology = topology;
}
@@ -66,7 +66,7 @@ export function findTopologyById(subTree, topologyId) {
}
export function updateNodeDegrees(nodes, edges) {
return nodes.map((node) => {
return nodes.map(node => {
const nodeId = node.get('id');
const degree = edges.count(edge => edge.get('source') === nodeId
|| edge.get('target') === nodeId);
@@ -76,7 +76,7 @@ export function updateNodeDegrees(nodes, edges) {
/* set topology.id and parentId for sub-topologies in place */
export function updateTopologyIds(topologies, parentId) {
return topologies.map((topology) => {
return topologies.map(topology => {
const result = Object.assign({}, topology);
result.id = topology.url.split('/').pop();
if (parentId) {
@@ -90,7 +90,7 @@ export function updateTopologyIds(topologies, parentId) {
}
export function addTopologyFullname(topologies) {
return topologies.map((t) => {
return topologies.map(t => {
if (!t.sub_topologies) {
return Object.assign({}, t, {fullName: t.name});
}
@@ -108,10 +108,10 @@ export function addTopologyFullname(topologies) {
export function setTopologyUrlsById(topologyUrlsById, topologies) {
let urlMap = topologyUrlsById;
if (topologies) {
topologies.forEach((topology) => {
topologies.forEach(topology => {
urlMap = urlMap.set(topology.id, topology.url);
if (topology.sub_topologies) {
topology.sub_topologies.forEach((subTopology) => {
topology.sub_topologies.forEach(subTopology => {
urlMap = urlMap.set(subTopology.id, subTopology.url);
});
}

View File

@@ -49,7 +49,7 @@ function consolidateBuffer() {
size(toUpdate), 'remove', size(toRemove));
// check if an added node in first was updated in second -> add second update
toAdd = map(toAdd, (node) => {
toAdd = map(toAdd, node => {
const updateNode = find(second.update, {id: node.id});
if (updateNode) {
toUpdate = reject(toUpdate, {id: node.id});
@@ -62,7 +62,7 @@ function consolidateBuffer() {
// no action needed, successive updates are fine
// check if an added node in first was removed in second -> dont add, dont remove
each(first.add, (node) => {
each(first.add, node => {
const removedNode = find(second.remove, {id: node.id});
if (removedNode) {
toAdd = reject(toAdd, {id: node.id});
@@ -71,7 +71,7 @@ function consolidateBuffer() {
});
// check if an updated node in first was removed in second -> remove
each(first.update, (node) => {
each(first.update, node => {
const removedNode = find(second.remove, {id: node.id});
if (removedNode) {
toUpdate = reject(toUpdate, {id: node.id});

View File

@@ -8,14 +8,7 @@
"dependencies": {
"babel-polyfill": "6.16.0",
"classnames": "2.2.5",
"d3-array": "1.0.2",
"d3-color": "1.0.2",
"d3-format": "1.0.2",
"d3-scale": "1.0.4",
"d3-selection": "1.0.3",
"d3-shape": "1.0.4",
"d3-time-format": "2.0.3",
"d3-zoom": "1.1.1",
"d3": "4.4.0",
"dagre": "0.7.4",
"debug": "2.3.3",
"filesize": "3.3.0",
@@ -45,7 +38,7 @@
"devDependencies": {
"autoprefixer": "6.5.3",
"babel-core": "6.18.2",
"babel-eslint": "7.1.1",
"babel-eslint": "5.0.0",
"babel-jest": "17.0.2",
"babel-loader": "6.2.8",
"babel-plugin-lodash": "3.2.10",
@@ -53,12 +46,11 @@
"babel-preset-react": "6.16.0",
"clean-webpack-plugin": "0.1.14",
"css-loader": "0.26.1",
"eslint": "3.11.1",
"eslint-config-airbnb": "13.0.0",
"eslint-loader": "1.6.1",
"eslint-plugin-import": "2.2.0",
"eslint-plugin-jsx-a11y": "2.2.3",
"eslint-plugin-react": "6.8.0",
"eslint": "2.4.0",
"eslint-config-airbnb": "6.1.0",
"eslint-loader": "1.3.0",
"eslint-plugin-jasmine": "1.6.0",
"eslint-plugin-react": "4.2.2",
"extract-text-webpack-plugin": "1.0.1",
"file-loader": "0.9.0",
"html-webpack-plugin": "2.24.1",