diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js index e1574574b..369238d1e 100644 --- a/client/app/scripts/components/debug-toolbar.js +++ b/client/app/scripts/components/debug-toolbar.js @@ -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.dispatch(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,26 +205,34 @@ 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); + } - log('added nodes', n); + _removeNode() { + const ns = this.props.nodes; + const nodeNames = ns.keySeq().toJS(); + return [nodeNames[_.random(nodeNames.length - 1)]]; } removeNode() { - const ns = this.props.nodes; - const nodeNames = ns.keySeq().toJS(); - this.props.dispatch(receiveNodesDelta({ - remove: [nodeNames[_.random(nodeNames.length - 1)]] + this.asyncDispatch(receiveNodesDelta({ + remove: this._removeNode() })); } @@ -223,12 +247,13 @@ class DebugToolbar extends React.Component { - - + +
diff --git a/client/app/scripts/selectors/chartSelectors.js b/client/app/scripts/selectors/chartSelectors.js index 24358b828..3a11871e6 100644 --- a/client/app/scripts/selectors/chartSelectors.js +++ b/client/app/scripts/selectors/chartSelectors.js @@ -1,9 +1,13 @@ +import debug from 'debug'; import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect'; -import { Map as makeMap, is } from 'immutable'; +import { Map as makeMap, is, Set } from 'immutable'; import { getAdjacentNodes } from '../utils/topology-utils'; +const log = debug('scope:selectors'); + + // // "immutable" createSelector // @@ -82,15 +86,18 @@ export const dataNodesSelector = createSelector( ); +// // FIXME: this is a bit of a hack... +// export const layoutNodesSelector = (_, props) => props.layoutNodes || makeMap(); -function mergeDeepIfExists(mapA, mapB) { +function mergeDeepKeyIntersection(mapA, mapB) { // - // Does a deep merge on any key that exists in the first map + // Does a deep merge on keys that exists in both maps // - return mapA.map((v, k) => v.mergeDeep(mapB.get(k))); + const commonKeys = Set.fromKeys(mapA).intersect(mapB.keySeq()); + return makeMap(commonKeys.map(k => [k, mapA.get(k).mergeDeep(mapB.get(k))])); } @@ -98,12 +105,15 @@ const _completeNodesSelector = createSelector( layoutNodesSelector, dataNodesSelector, (layoutNodes, dataNodes) => { - if (layoutNodes.size === 0 || dataNodes.size === 0) { - return makeMap(); + // + // 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); } - - // dataNodes might get updated before layoutNodes when a node is removed from the topo. - return mergeDeepIfExists(dataNodes, layoutNodes); + return mergeDeepKeyIntersection(dataNodes, layoutNodes); } );