Merge pull request #1831 from weaveworks/1829-fixes-moc-updating

Fixes metrics-on-canvas updating.
This commit is contained in:
Simon
2016-09-28 11:47:40 +02:00
committed by GitHub
13 changed files with 369 additions and 224 deletions

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { completeNodesSelector } from '../selectors/chartSelectors';
import NodesChartEdges from './nodes-chart-edges';
import NodesChartNodes from './nodes-chart-nodes';
@@ -9,14 +10,24 @@ class NodesChartElements extends React.Component {
const props = this.props;
return (
<g className="nodes-chart-elements" transform={props.transform}>
<NodesChartEdges layoutEdges={props.layoutEdges}
<NodesChartEdges
layoutEdges={props.layoutEdges}
layoutPrecision={props.layoutPrecision} />
<NodesChartNodes layoutNodes={props.layoutNodes} nodeScale={props.nodeScale}
scale={props.scale} selectedNodeScale={props.selectedNodeScale}
<NodesChartNodes
layoutNodes={props.completeNodes}
nodeScale={props.nodeScale}
scale={props.scale}
selectedNodeScale={props.selectedNodeScale}
layoutPrecision={props.layoutPrecision} />
</g>
);
}
}
export default connect()(NodesChartElements);
function mapStateToProps(state, props) {
return {
completeNodes: completeNodesSelector(state, props)
};
}
export default connect(mapStateToProps)(NodesChartElements);

View File

@@ -3,17 +3,17 @@ import d3 from 'd3';
import debug from 'debug';
import React from 'react';
import { connect } from 'react-redux';
import { Map as makeMap, fromJS, is as isDeepEqual } from 'immutable';
import { Map as makeMap, fromJS } from 'immutable';
import timely from 'timely';
import { nodeAdjacenciesSelector, adjacentNodesSelector } from '../selectors/chartSelectors';
import { clickBackground } from '../actions/app-actions';
import { EDGE_ID_SEPARATOR } from '../constants/naming';
import { MIN_NODE_SIZE, DETAILS_PANEL_WIDTH, MAX_NODE_SIZE } from '../constants/styles';
import Logo from '../components/logo';
import { doLayout } from './nodes-layout';
import NodesChartElements from './nodes-chart-elements';
import { getActiveTopologyOptions, getAdjacentNodes,
isSameTopology } from '../utils/topology-utils';
import { getActiveTopologyOptions } from '../utils/topology-utils';
const log = debug('scope:nodes-chart');
@@ -24,6 +24,94 @@ const radiusDensity = d3.scale.threshold()
.domain([3, 6])
.range([2.5, 3.5, 3]);
/**
* dynamic coords precision based on topology size
*/
function getLayoutPrecision(nodesCount) {
let precision;
if (nodesCount >= 50) {
precision = 0;
} else if (nodesCount > 20) {
precision = 1;
} else if (nodesCount > 10) {
precision = 2;
} else {
precision = 3;
}
return precision;
}
function initEdges(nodes) {
let edges = makeMap();
nodes.forEach((node, nodeId) => {
const adjacency = node.get('adjacency');
if (adjacency) {
adjacency.forEach(adjacent => {
const edge = [nodeId, adjacent];
const edgeId = edge.join(EDGE_ID_SEPARATOR);
if (!edges.has(edgeId)) {
const source = edge[0];
const target = edge[1];
if (nodes.has(source) && nodes.has(target)) {
edges = edges.set(edgeId, makeMap({
id: edgeId,
value: 1,
source,
target
}));
}
}
});
}
});
return edges;
}
function getNodeScale(nodesCount, width, height) {
const expanse = Math.min(height, width);
const nodeSize = expanse / 3; // single node should fill a third of the screen
const maxNodeSize = Math.min(MAX_NODE_SIZE, expanse / 10);
const normalizedNodeSize = Math.max(MIN_NODE_SIZE,
Math.min(nodeSize / Math.sqrt(nodesCount), maxNodeSize));
return d3.scale.linear().range([0, normalizedNodeSize]);
}
function updateLayout(width, height, nodes, baseOptions) {
const nodeScale = getNodeScale(nodes.size, width, height);
const edges = initEdges(nodes);
const options = Object.assign({}, baseOptions, {
scale: nodeScale,
});
const timedLayouter = timely(doLayout);
const graph = timedLayouter(nodes, edges, options);
log(`graph layout took ${timedLayouter.time}ms`);
const layoutNodes = graph.nodes.map(node => makeMap({
x: node.get('x'),
y: node.get('y'),
// extract coords and save for restore
px: node.get('x'),
py: node.get('y')
}));
const layoutEdges = graph.edges
.map(edge => edge.set('ppoints', edge.get('points')));
return { layoutNodes, layoutEdges, layoutWidth: graph.width, layoutHeight: graph.height };
}
class NodesChart extends React.Component {
constructor(props, context) {
@@ -43,7 +131,7 @@ class NodesChart extends React.Component {
hasZoomed: false,
height: props.height || 0,
width: props.width || 0,
zoomCache: {}
zoomCache: {},
};
}
@@ -82,8 +170,7 @@ class NodesChart extends React.Component {
state.height = nextProps.forceRelayout ? nextProps.height : (state.height || nextProps.height);
state.width = nextProps.forceRelayout ? nextProps.width : (state.width || nextProps.width);
// _.assign(state, this.updateGraphState(nextProps, state));
if (nextProps.forceRelayout || !isSameTopology(nextProps.nodes, this.props.nodes)) {
if (nextProps.forceRelayout || nextProps.nodes !== this.props.nodes) {
_.assign(state, this.updateGraphState(nextProps, state));
}
@@ -127,6 +214,7 @@ class NodesChart extends React.Component {
const transform = `translate(${translate}) scale(${scale})`;
const svgClassNames = this.props.isEmpty ? 'hide' : '';
const layoutPrecision = getLayoutPrecision(nodes.size);
return (
<div className="nodes-chart">
<svg width="100%" height="100%" id="nodes-chart-canvas"
@@ -134,10 +222,14 @@ class NodesChart extends React.Component {
<g transform="translate(24,24) scale(0.25)">
<Logo />
</g>
<NodesChartElements layoutNodes={nodes} layoutEdges={edges}
nodeScale={this.state.nodeScale} scale={scale} transform={transform}
<NodesChartElements
layoutNodes={nodes}
layoutEdges={edges}
nodeScale={this.state.nodeScale}
scale={scale}
transform={transform}
selectedNodeScale={this.state.selectedNodeScale}
layoutPrecision={this.props.layoutPrecision} />
layoutPrecision={layoutPrecision} />
</svg>
</div>
);
@@ -151,70 +243,10 @@ class NodesChart extends React.Component {
}
}
initNodes(topology, stateNodes) {
let nextStateNodes = stateNodes;
// remove nodes that have disappeared
stateNodes.forEach((node, id) => {
if (!topology.has(id)) {
nextStateNodes = nextStateNodes.delete(id);
}
});
// copy relevant fields to state nodes
topology.forEach((node, id) => {
nextStateNodes = nextStateNodes.mergeIn([id], makeMap({
id,
label: node.get('label'),
pseudo: node.get('pseudo'),
subLabel: node.get('label_minor'),
nodeCount: node.get('node_count'),
metrics: node.get('metrics'),
rank: node.get('rank'),
shape: node.get('shape'),
stack: node.get('stack'),
networks: node.get('networks'),
}));
});
return nextStateNodes;
}
initEdges(topology, stateNodes) {
let edges = makeMap();
topology.forEach((node, nodeId) => {
const adjacency = node.get('adjacency');
if (adjacency) {
adjacency.forEach(adjacent => {
const edge = [nodeId, adjacent];
const edgeId = edge.join(EDGE_ID_SEPARATOR);
if (!edges.has(edgeId)) {
const source = edge[0];
const target = edge[1];
if (stateNodes.has(source) && stateNodes.has(target)) {
edges = edges.set(edgeId, makeMap({
id: edgeId,
value: 1,
source,
target
}));
}
}
});
}
});
return edges;
}
centerSelectedNode(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
const selectedLayoutNode = stateNodes.get(props.selectedNodeId);
if (!selectedLayoutNode) {
if (!stateNodes.has(props.selectedNodeId)) {
return {};
}
@@ -245,8 +277,8 @@ class NodesChart extends React.Component {
const radius = Math.min(state.width, state.height) / density / zoomScale;
const offsetAngle = Math.PI / 4;
stateNodes = stateNodes.map((node) => {
const index = adjacentLayoutNodeIds.indexOf(node.get('id'));
stateNodes = stateNodes.map((node, nodeId) => {
const index = adjacentLayoutNodeIds.indexOf(nodeId);
if (index > -1) {
const angle = offsetAngle + Math.PI * 2 * index / adjacentCount;
return node.merge({
@@ -259,8 +291,8 @@ class NodesChart extends React.Component {
// fix all edges for circular nodes
stateEdges = stateEdges.map(edge => {
if (edge.get('source') === selectedLayoutNode.get('id')
|| edge.get('target') === selectedLayoutNode.get('id')
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'));
@@ -274,7 +306,7 @@ class NodesChart extends React.Component {
});
// auto-scale node size for selected nodes
const selectedNodeScale = this.getNodeScale(adjacentNodes, state.width, state.height);
const selectedNodeScale = getNodeScale(adjacentNodes.size, state.width, state.height);
return {
selectedNodeScale,
@@ -311,44 +343,23 @@ class NodesChart extends React.Component {
};
}
const stateNodes = this.initNodes(props.nodes, state.nodes);
const stateEdges = this.initEdges(props.nodes, stateNodes);
const nodeScale = this.getNodeScale(props.nodes, state.width, state.height);
const nextState = { nodeScale };
const options = {
width: state.width,
height: state.height,
scale: nodeScale,
margins: props.margins,
forceRelayout: props.forceRelayout,
topologyId: this.props.topologyId,
topologyOptions: this.props.topologyOptions
topologyId: props.topologyId,
topologyOptions: props.topologyOptions,
};
const timedLayouter = timely(doLayout);
const graph = timedLayouter(stateNodes, stateEdges, options);
log(`graph layout took ${timedLayouter.time}ms`);
// extract coords and save for restore
const graphNodes = graph.nodes.map(node => makeMap({
x: node.get('x'),
px: node.get('x'),
y: node.get('y'),
py: node.get('y')
}));
const layoutNodes = stateNodes.mergeDeep(graphNodes);
const layoutEdges = graph.edges
.map(edge => edge.set('ppoints', edge.get('points')));
const { layoutNodes, layoutEdges, layoutWidth, layoutHeight } = updateLayout(
state.width, state.height, props.nodes, options);
//
// adjust layout based on viewport
const xFactor = (state.width - props.margins.left - props.margins.right) / graph.width;
const yFactor = state.height / graph.height;
const xFactor = (state.width - props.margins.left - props.margins.right) / layoutWidth;
const yFactor = state.height / layoutHeight;
const zoomFactor = Math.min(xFactor, yFactor);
let zoomScale = this.state.scale;
let zoomScale = state.scale;
if (this.zoom && !state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
zoomScale = zoomFactor;
@@ -356,24 +367,12 @@ class NodesChart extends React.Component {
this.zoom.scale(zoomFactor);
}
nextState.scale = zoomScale;
if (!isDeepEqual(layoutNodes, state.nodes)) {
nextState.nodes = layoutNodes;
}
if (!isDeepEqual(layoutEdges, state.edges)) {
nextState.edges = layoutEdges;
}
return nextState;
}
getNodeScale(nodes, width, height) {
const expanse = Math.min(height, width);
const nodeSize = expanse / 3; // single node should fill a third of the screen
const maxNodeSize = Math.min(MAX_NODE_SIZE, expanse / 10);
const normalizedNodeSize = Math.max(MIN_NODE_SIZE,
Math.min(nodeSize / Math.sqrt(nodes.size), maxNodeSize));
return this.state.nodeScale.copy().range([0, normalizedNodeSize]);
return {
scale: zoomScale,
nodes: layoutNodes,
edges: layoutEdges,
nodeScale: getNodeScale(props.nodes.size, state.width, state.height),
};
}
zoomed() {
@@ -391,9 +390,11 @@ class NodesChart extends React.Component {
}
}
function mapStateToProps(state) {
return {
adjacentNodes: getAdjacentNodes(state),
nodes: nodeAdjacenciesSelector(state),
adjacentNodes: adjacentNodesSelector(state),
forceRelayout: state.get('forceRelayout'),
selectedNodeId: state.get('selectedNodeId'),
topologyId: state.get('currentTopologyId'),
@@ -401,6 +402,7 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps,
{ clickBackground }

View File

@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { List as makeList, Map as makeMap } from 'immutable';
import NodeDetailsTable from '../components/node-details/node-details-table';
import { clickNode, sortOrderChanged } from '../actions/app-actions';
import { nodesSelector } from '../selectors/chartSelectors';
import { getNodeColor } from '../utils/color-utils';
@@ -142,6 +143,7 @@ class NodesGrid extends React.Component {
function mapStateToProps(state) {
return {
nodes: nodesSelector(state),
gridSortBy: state.get('gridSortBy'),
gridSortedDesc: state.get('gridSortedDesc'),
currentTopology: state.get('currentTopology'),

View File

@@ -142,6 +142,7 @@ class App extends React.Component {
}
}
function mapStateToProps(state) {
return {
activeTopologyOptions: getActiveTopologyOptions(state),
@@ -158,6 +159,7 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(App);

View File

@@ -43,9 +43,6 @@ const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1)
.map(n => String.fromCharCode(n));
// const randomLetter = () => _.sample(LABEL_PREFIXES);
const deltaAdd = (
name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1,
networks = NETWORKS
@@ -71,7 +68,7 @@ function addMetrics(availableMetrics, node, v) {
]);
return Object.assign({}, node, {
metrics: metrics.map(m => Object.assign({}, m, {max: 100, value: v}))
metrics: metrics.map(m => Object.assign({}, m, {label: 'zing', max: 100, value: v})).toJS()
});
}
@@ -94,14 +91,16 @@ function addAllVariants(dispatch) {
}
function addAllMetricVariants(availableMetrics, dispatch) {
function addAllMetricVariants(availableMetrics) {
const newNodes = _.flattenDeep(METRIC_FILLS.map((v, i) => (
SHAPES.map(s => [addMetrics(availableMetrics, deltaAdd(label(s) + i, [], s), v)])
)));
dispatch(receiveNodesDelta({
add: newNodes
}));
return (dispatch) => {
dispatch(receiveNodesDelta({
add: newNodes
}));
};
}
@@ -177,11 +176,28 @@ class DebugToolbar extends React.Component {
});
}
setLoading(loading) {
this.props.setAppState(state => state.set('topologiesLoaded', !loading));
asyncDispatch(v) {
setTimeout(() => this.props.dispatch(v), 0);
}
addNodes(n, prefix = 'zing') {
setLoading(loading) {
this.asyncDispatch(setAppState(state => state.set('topologiesLoaded', !loading)));
}
updateAdjacencies() {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
this.asyncDispatch(receiveNodesDelta({
add: this._addNodes(7),
update: sample(nodeNames).map(n => ({
id: n,
adjacency: sample(nodeNames),
}), nodeNames.length),
remove: this._removeNode(),
}));
}
_addNodes(n, prefix = 'zing') {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
const newNodeNames = _.range(ns.size, ns.size + n).map(i => (
@@ -189,19 +205,35 @@ class DebugToolbar extends React.Component {
`${prefix}${i}`
));
const allNodes = _(nodeNames).concat(newNodeNames).value();
return newNodeNames.map((name) => deltaAdd(
name,
sample(allNodes),
_.sample(SHAPES),
_.sample(STACK_VARIANTS),
_.sample(NODE_COUNTS),
sample(NETWORKS, 10)
));
}
this.props.dispatch(receiveNodesDelta({
add: newNodeNames.map((name) => deltaAdd(
name,
sample(allNodes),
_.sample(SHAPES),
_.sample(STACK_VARIANTS),
_.sample(NODE_COUNTS),
sample(NETWORKS, 10)
))
addNodes(n, prefix = 'zing') {
setTimeout(() => {
this.asyncDispatch(receiveNodesDelta({
add: this._addNodes(n, prefix)
}));
log('added nodes', n);
}, 0);
}
_removeNode() {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
return [nodeNames[_.random(nodeNames.length - 1)]];
}
removeNode() {
this.asyncDispatch(receiveNodesDelta({
remove: this._removeNode()
}));
log('added nodes', n);
}
render() {
@@ -215,11 +247,13 @@ class DebugToolbar extends React.Component {
<button onClick={() => this.addNodes(10)}>+10</button>
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
<button onClick={() => this.addNodes(this.state.nodesToAdd)}>+</button>
<button onClick={() => addAllVariants(this.props.dispatch)}>Variants</button>
<button onClick={() => addAllMetricVariants(availableCanvasMetrics, this.props.dispatch)}>
<button onClick={() => this.asyncDispatch(addAllVariants)}>Variants</button>
<button onClick={() => this.asyncDispatch(addAllMetricVariants(availableCanvasMetrics))}>
Metric Variants
</button>
<button onClick={() => this.addNodes(1, LOREM)}>Long name</button>
<button onClick={() => this.removeNode()}>Remove node</button>
<button onClick={() => this.updateAdjacencies()}>Update adj.</button>
</div>
<div>
@@ -289,6 +323,5 @@ function mapStateToProps(state) {
export default connect(
mapStateToProps,
{setAppState}
mapStateToProps
)(DebugToolbar);

View File

@@ -13,24 +13,6 @@ const navbarHeight = 194;
const marginTop = 0;
/**
* dynamic coords precision based on topology size
*/
function getLayoutPrecision(nodesCount) {
let precision;
if (nodesCount >= 50) {
precision = 0;
} else if (nodesCount > 20) {
precision = 1;
} else if (nodesCount > 10) {
precision = 2;
} else {
precision = 3;
}
return precision;
}
class Nodes extends React.Component {
constructor(props, context) {
super(props, context);
@@ -67,9 +49,8 @@ class Nodes extends React.Component {
}
render() {
const { nodes, topologyEmpty, gridMode, topologiesLoaded, nodesLoaded, topologies,
const { topologyEmpty, gridMode, topologiesLoaded, nodesLoaded, topologies,
currentTopology } = this.props;
const layoutPrecision = getLayoutPrecision(nodes.size);
return (
<div className="nodes-wrapper">
@@ -84,13 +65,10 @@ class Nodes extends React.Component {
{gridMode ?
<NodesGrid {...this.state}
nodeSize="24"
nodes={nodes}
margins={CANVAS_MARGINS}
/> :
<NodesChart {...this.state}
nodes={nodes}
margins={CANVAS_MARGINS}
layoutPrecision={layoutPrecision}
/>}
</div>
);
@@ -113,7 +91,6 @@ function mapStateToProps(state) {
return {
currentTopology: state.get('currentTopology'),
gridMode: state.get('gridMode'),
nodes: state.get('nodes').filter(node => !node.get('filtered')),
nodesLoaded: state.get('nodesLoaded'),
topologies: state.get('topologies'),
topologiesLoaded: state.get('topologiesLoaded'),

View File

@@ -0,0 +1,118 @@
import debug from 'debug';
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
import { Map as makeMap, is, Set } from 'immutable';
import { getAdjacentNodes } from '../utils/topology-utils';
const log = debug('scope:selectors');
//
// `mergeDeepKeyIntersection` does a deep merge on keys that exists in both maps
//
function mergeDeepKeyIntersection(mapA, mapB) {
const commonKeys = Set.fromKeys(mapA).intersect(mapB.keySeq());
return makeMap(commonKeys.map(k => [k, mapA.get(k).mergeDeep(mapB.get(k))]));
}
//
// `returnPreviousRefIfEqual` is a helper function that checks the new computed of a selector
// against the previously computed value. If they are deeply equal return the previous result. This
// is important for things like connect() which tests whether componentWillReceiveProps should be
// called by doing a '===' on the values you return from mapStateToProps.
//
// e.g.
//
// const filteredThings = createSelector(
// state => state.things,
// (things) => things.filter(t => t > 2)
// );
//
// // This will trigger componentWillReceiveProps on every store change:
// connect(s => { things: filteredThings(s) }, ThingComponent);
//
// // But if we wrap it, the result will be === if it `is()` equal and...
// const filteredThingsWrapped = returnPreviousRefIfEqual(filteredThings);
//
// // ...We're safe!
// connect(s => { things: filteredThingsWrapped(s) }, ThingComponent);
//
// Note: This is a slightly strange way to use reselect. Selectors memoize their *arguments* not
// "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 _identity = v => v;
const returnPreviousRefIfEqual = (selector) => _createDeepEqualSelector(selector, _identity);
//
// Selectors!
//
const allNodesSelector = state => state.get('nodes');
export const nodesSelector = returnPreviousRefIfEqual(
createSelector(
allNodesSelector,
(allNodes) => allNodes.filter(node => !node.get('filtered'))
)
);
export const adjacentNodesSelector = returnPreviousRefIfEqual(getAdjacentNodes);
export const nodeAdjacenciesSelector = returnPreviousRefIfEqual(
createSelector(
nodesSelector,
(nodes) => nodes.map(n => makeMap({
id: n.get('id'),
adjacency: n.get('adjacency'),
}))
)
);
export const dataNodesSelector = createSelector(
nodesSelector,
(nodes) => nodes.map((node, id) => makeMap({
id,
label: node.get('label'),
pseudo: node.get('pseudo'),
subLabel: node.get('label_minor'),
nodeCount: node.get('node_count'),
metrics: node.get('metrics'),
rank: node.get('rank'),
shape: node.get('shape'),
stack: node.get('stack'),
networks: node.get('networks'),
}))
);
//
// FIXME: this is a bit of a hack...
//
export const layoutNodesSelector = (_, props) => props.layoutNodes || makeMap();
export const completeNodesSelector = createSelector(
layoutNodesSelector,
dataNodesSelector,
(layoutNodes, dataNodes) => {
//
// There are no guarantees whether this selector will be computed first (when
// node-chart-elements.mapStateToProps is called by store.subscribe before
// nodes-chart.mapStateToProps is called), and component render batching and yadada.
//
if (layoutNodes.size !== dataNodes.size) {
log('Obviously mismatched node data', layoutNodes.size, dataNodes.size);
}
return mergeDeepKeyIntersection(dataNodes, layoutNodes);
}
);

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import { is as isDeepEqual, Map as makeMap, Set as makeSet, List as makeList } from 'immutable';
import { Set as makeSet, List as makeList } from 'immutable';
//
@@ -143,6 +143,7 @@ export function isTopologyEmpty(state) {
&& state.get('nodes').size === 0;
}
export function getAdjacentNodes(state, originNodeId) {
let adjacentNodes = makeSet();
const nodeId = originNodeId || state.get('selectedNodeId');
@@ -171,13 +172,6 @@ export function getCurrentTopologyUrl(state) {
return state.getIn(['currentTopology', 'url']);
}
export function isSameTopology(nodes, nextNodes) {
const mapper = node => makeMap({id: node.get('id'), adjacency: node.get('adjacency')});
const topology = nodes.map(mapper);
const nextTopology = nextNodes.map(mapper);
return isDeepEqual(topology, nextTopology);
}
export function isNodeMatchingQuery(node, query) {
return node.get('label').includes(query) || node.get('subLabel').includes(query);
}

View File

@@ -29,6 +29,7 @@
"redux-logger": "2.6.1",
"redux-thunk": "2.0.1",
"reqwest": "~2.0.5",
"reselect": "^2.5.3",
"timely": "0.1.0",
"whatwg-fetch": "0.11.0"
},

View File

@@ -66,7 +66,7 @@ if (process.env.NODE_ENV !== 'production') {
hot: true,
noInfo: true,
historyApiFallback: true,
stats: { colors: true }
stats: 'errors-only',
}).listen(4041, '0.0.0.0', function (err, result) {
if (err) {
console.log(err);

View File

@@ -18,85 +18,84 @@ function clickIfVisible(list, index) {
});
}
function selectNode(browser) {
debug('select node');
return browser.elementByCssSelector('.nodes-chart-nodes .node:nth-child(1) > g', function(err, el) {
return el.click();
});
}
function deselectNode(browser) {
debug('deselect node');
return browser.elementByCssSelector('.fa-close', function(err, el) {
return el.click();
});
}
module.exports = function(cfg) {
var startUrl = 'http://' + cfg.host + '/debug.html';
var selectedUrl = 'http://' + cfg.host + '/debug.html#!/state/{"nodeDetails":[{"id":"zing11","label":"zing11","topologyId":"containers"}],"selectedNodeId":"zing11","topologyId":"containers","topologyOptions":{"processes":{"unconnected":"hide"},"processes-by-name":{"unconnected":"hide"},"containers":{"system":"hide","stopped":"hide"},"containers-by-hostname":{"system":"hide","stopped":"hide"},"containers-by-image":{"system":"hide","stopped":"hide"}}}';
var startUrl = 'http://' + cfg.host + '/';
// cfg - The configuration object. args, from the example above.
return function(browser) {
// browser is created using wd.promiseRemote()
// More info about wd at https://github.com/admc/wd
return browser.get('http://' + cfg.host + '/debug.html')
return browser.get('http://' + cfg.host + '/')
.then(function() {
debug('starting run ' + cfg.run);
return browser.sleep(2000);
})
.then(function() {
return browser.execute("localStorage.debugToolbar = 1;");
})
.then(function() {
return browser.sleep(5000);
})
.then(function() {
return browser.elementByCssSelector('.debug-panel button:nth-child(5)');
// return browser.elementByCssSelector('.debug-panel div:nth-child(2) button:nth-child(9)');
})
.then(function(el) {
debug('debug-panel found');
return el.click(function() {
el.click(function() {
el.click();
});
});
return el.click();
})
.then(function() {
return browser.sleep(2000);
})
.then(function() {
return browser.sleep(2000);
})
.then(function() {
debug('select node');
return browser.get(selectedUrl);
return selectNode(browser);
})
.then(function() {
return browser.sleep(5000);
})
.then(function() {
debug('deselect node');
return browser.elementByCssSelector('.fa-close', function(err, el) {
return el.click();
});
return deselectNode(browser);
})
.then(function() {
return browser.sleep(2000);
})
.then(function() {
debug('select node');
return browser.get(selectedUrl);
return selectNode(browser);
})
.then(function() {
return browser.sleep(5000);
})
.then(function() {
debug('deselect node');
return browser.elementByCssSelector('.fa-close', function(err, el) {
return el.click();
});
return deselectNode(browser);
})
.then(function() {
return browser.sleep(2000);
})
.then(function() {
debug('select node');
return browser.get(selectedUrl);
return selectNode(browser);
})
.then(function() {
return browser.sleep(5000);
})
.then(function() {
debug('deselect node');
return browser.elementByCssSelector('.fa-close', function(err, el) {
return el.click();
});
return deselectNode(browser);
})
.then(function() {

View File

@@ -3,7 +3,7 @@ var options = {
selenium: 'http://local.docker:4444/wd/hub',
actions: [require('./custom-action.js')()]
}
browserPerf('http://local.docker:4040/debug.html', function(err, res){
browserPerf('http://local.docker:4040/dev.html', function(err, res){
console.error(err);
console.log(res);
}, options);

View File

@@ -10,6 +10,12 @@
#
# perfjankie --only-update-site --couch-server http://local.docker:5984 --couch-database performance
#
# Optional: run from localhost which can be a bit fast than rebuilding...
# - ssh -R 0.0.0.0:4042:localhost:4042 docker@local.docker
# - npm run build
# - BACKEND_HOST=local.docker npm start
# - ./run-jankie.sh 192.168.64.3:4042
#
# Usage:
#
# ./run-jankie.sh 192.168.64.3:4040
@@ -26,9 +32,9 @@ COMMIT=$(git log --format="%h" -1)
echo "Testing $COMMIT on $DATE"
../../scope stop
make SUDO= -C ../..
../../scope launch
sleep 5
# ../../scope stop
# make SUDO= -C ../..
# ../../scope launch
# sleep 5
COMMIT="$COMMIT" DATE=$DATE HOST=$HOST DEBUG=scope* node ./perfjankie/main.js