mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
Moved the node networks computation to selectors.
This commit is contained in:
@@ -404,6 +404,10 @@ export function focusSearch() {
|
||||
dispatch({ type: ActionTypes.FOCUS_SEARCH });
|
||||
// update nodes cache to allow search across all topologies,
|
||||
// wait a second until animation is over
|
||||
// NOTE: This will cause matching recalculation (and rerendering)
|
||||
// of all the nodes in the topology, instead applying it only on
|
||||
// the nodes delta. The solution would be to implement deeper
|
||||
// search selectors with per-node caching instead of per-topology.
|
||||
setTimeout(() => {
|
||||
getAllNodes(getState, dispatch);
|
||||
}, 1200);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { List as makeList } from 'immutable';
|
||||
|
||||
import { selectedNetworkNodesIdsSelector } from '../selectors/node-networks';
|
||||
import { currentTopologySearchNodeMatchesSelector } from '../selectors/search';
|
||||
import { hasSelectedNode as hasSelectedNodeFn } from '../utils/topology-utils';
|
||||
import EdgeContainer from './edge-container';
|
||||
@@ -9,7 +9,7 @@ import EdgeContainer from './edge-container';
|
||||
class NodesChartEdges extends React.Component {
|
||||
render() {
|
||||
const { hasSelectedNode, highlightedEdgeIds, layoutEdges, searchQuery,
|
||||
isAnimated, selectedScale, selectedNodeId, selectedNetwork, selectedNetworkNodes,
|
||||
isAnimated, selectedScale, selectedNodeId, selectedNetwork, selectedNetworkNodesIds,
|
||||
searchNodeMatches } = this.props;
|
||||
|
||||
return (
|
||||
@@ -24,8 +24,8 @@ class NodesChartEdges extends React.Component {
|
||||
!(searchNodeMatches.has(edge.get('source')) &&
|
||||
searchNodeMatches.has(edge.get('target')));
|
||||
const noSelectedNetworks = selectedNetwork &&
|
||||
!(selectedNetworkNodes.contains(edge.get('source')) &&
|
||||
selectedNetworkNodes.contains(edge.get('target')));
|
||||
!(selectedNetworkNodesIds.contains(edge.get('source')) &&
|
||||
selectedNetworkNodesIds.contains(edge.get('target')));
|
||||
const blurred = !highlighted && (otherNodesSelected ||
|
||||
(!focused && noMatches) ||
|
||||
(!focused && noSelectedNetworks));
|
||||
@@ -57,7 +57,7 @@ export default connect(
|
||||
searchNodeMatches: currentTopologySearchNodeMatchesSelector(state),
|
||||
searchQuery: state.get('searchQuery'),
|
||||
selectedNetwork: state.get('selectedNetwork'),
|
||||
selectedNetworkNodes: state.getIn(['networkNodes', state.get('selectedNetwork')], makeList()),
|
||||
selectedNetworkNodesIds: selectedNetworkNodesIdsSelector(state),
|
||||
selectedNodeId: state.get('selectedNodeId'),
|
||||
})
|
||||
)(NodesChartEdges);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { List as makeList } from 'immutable';
|
||||
|
||||
import { nodeMetricsSelector } from '../selectors/metrics';
|
||||
import { nodeNetworksSelector, selectedNetworkNodesIdsSelector } from '../selectors/node-networks';
|
||||
import { nodeMetricSelector } from '../selectors/node-metric';
|
||||
import { currentTopologySearchNodeMatchesSelector } from '../selectors/search';
|
||||
import { getAdjacentNodes } from '../utils/topology-utils';
|
||||
import NodeContainer from './node-container';
|
||||
@@ -10,8 +10,8 @@ import NodeContainer from './node-container';
|
||||
class NodesChartNodes extends React.Component {
|
||||
render() {
|
||||
const { adjacentNodes, highlightedNodeIds, layoutNodes, isAnimated,
|
||||
mouseOverNodeId, nodeMetrics, selectedScale, searchQuery, selectedNetwork,
|
||||
selectedNodeId, searchNodeMatches } = this.props;
|
||||
mouseOverNodeId, nodeMetric, selectedScale, searchQuery, selectedNetwork,
|
||||
selectedNodeId, searchNodeMatches, nodeNetworks, selectedNetworkNodesIds } = this.props;
|
||||
|
||||
// highlighter functions
|
||||
const setHighlighted = node => node.set('highlighted',
|
||||
@@ -22,7 +22,7 @@ class NodesChartNodes extends React.Component {
|
||||
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)));
|
||||
|| (selectedNetwork && !selectedNetworkNodesIds.contains(node.get('id'))));
|
||||
|
||||
// make sure blurred nodes are in the background
|
||||
const sortNodes = (node) => {
|
||||
@@ -46,26 +46,32 @@ class NodesChartNodes extends React.Component {
|
||||
|
||||
return (
|
||||
<g className="nodes-chart-nodes">
|
||||
{nodesToRender.map(node => <NodeContainer
|
||||
blurred={node.get('blurred')}
|
||||
focused={node.get('focused')}
|
||||
matches={searchNodeMatches.get(node.get('id'))}
|
||||
highlighted={node.get('highlighted')}
|
||||
shape={node.get('shape')}
|
||||
networks={node.get('networks')}
|
||||
stack={node.get('stack')}
|
||||
key={node.get('id')}
|
||||
id={node.get('id')}
|
||||
label={node.get('label')}
|
||||
labelMinor={node.get('labelMinor')}
|
||||
pseudo={node.get('pseudo')}
|
||||
metric={nodeMetrics.get(node.get('id'))}
|
||||
rank={node.get('rank')}
|
||||
isAnimated={isAnimated}
|
||||
scale={node.get('focused') ? selectedScale : 1}
|
||||
dx={node.get('x')}
|
||||
dy={node.get('y')}
|
||||
/>)}
|
||||
{nodesToRender.map((node) => {
|
||||
const nodeScale = node.get('focused') ? selectedScale : 1;
|
||||
const nodeId = node.get('id');
|
||||
return (
|
||||
<NodeContainer
|
||||
matches={searchNodeMatches.get(nodeId)}
|
||||
networks={nodeNetworks.get(nodeId)}
|
||||
metric={nodeMetric.get(nodeId)}
|
||||
blurred={node.get('blurred')}
|
||||
focused={node.get('focused')}
|
||||
highlighted={node.get('highlighted')}
|
||||
shape={node.get('shape')}
|
||||
stack={node.get('stack')}
|
||||
key={node.get('id')}
|
||||
id={node.get('id')}
|
||||
label={node.get('label')}
|
||||
labelMinor={node.get('labelMinor')}
|
||||
pseudo={node.get('pseudo')}
|
||||
rank={node.get('rank')}
|
||||
dx={node.get('x')}
|
||||
dy={node.get('y')}
|
||||
scale={nodeScale}
|
||||
isAnimated={isAnimated}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
@@ -74,12 +80,14 @@ class NodesChartNodes extends React.Component {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
adjacentNodes: getAdjacentNodes(state),
|
||||
nodeMetrics: nodeMetricsSelector(state),
|
||||
nodeMetric: nodeMetricSelector(state),
|
||||
nodeNetworks: nodeNetworksSelector(state),
|
||||
searchNodeMatches: currentTopologySearchNodeMatchesSelector(state),
|
||||
selectedNetworkNodesIds: selectedNetworkNodesIdsSelector(state),
|
||||
highlightedNodeIds: state.get('highlightedNodeIds'),
|
||||
mouseOverNodeId: state.get('mouseOverNodeId'),
|
||||
selectedNetwork: state.get('selectedNetwork'),
|
||||
selectedNodeId: state.get('selectedNodeId'),
|
||||
searchNodeMatches: currentTopologySearchNodeMatchesSelector(state),
|
||||
searchQuery: state.get('searchQuery'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Map as makeMap } from 'immutable';
|
||||
import { event as d3Event, select } from 'd3-selection';
|
||||
import { zoom, zoomIdentity } from 'd3-zoom';
|
||||
|
||||
import { nodeAdjacenciesSelector } from '../selectors/nodes';
|
||||
import { shownNodesSelector } from '../selectors/node-filters';
|
||||
import { clickBackground } from '../actions/app-actions';
|
||||
import Logo from '../components/logo';
|
||||
import NodesChartElements from './nodes-chart-elements';
|
||||
@@ -172,7 +172,7 @@ class NodesChart extends React.Component {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
nodes: nodeAdjacenciesSelector(state),
|
||||
nodes: shownNodesSelector(state),
|
||||
forceRelayout: state.get('forceRelayout'),
|
||||
selectedNodeId: state.get('selectedNodeId'),
|
||||
topologyId: state.get('currentTopologyId'),
|
||||
|
||||
@@ -5,7 +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 { shownNodesSelector } from '../selectors/nodes';
|
||||
import { shownNodesSelector } from '../selectors/node-filters';
|
||||
|
||||
import { currentTopologySearchNodeMatchesSelector } from '../selectors/search';
|
||||
import { getNodeColor } from '../utils/color-utils';
|
||||
|
||||
@@ -22,6 +22,7 @@ import NetworkSelector from './networks-selector';
|
||||
import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar';
|
||||
import { getRouter, getUrlState } from '../utils/router-utils';
|
||||
import { getActiveTopologyOptions } from '../utils/topology-utils';
|
||||
import { availableNetworksSelector } from '../selectors/node-networks';
|
||||
|
||||
const BACKSPACE_KEY_CODE = 8;
|
||||
const ENTER_KEY_CODE = 13;
|
||||
@@ -151,7 +152,7 @@ function mapStateToProps(state) {
|
||||
showingHelp: state.get('showingHelp'),
|
||||
showingTroubleshootingMenu: state.get('showingTroubleshootingMenu'),
|
||||
showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0,
|
||||
showingNetworkSelector: state.get('availableNetworks').count() > 0,
|
||||
showingNetworkSelector: availableNetworksSelector(state).count() > 0,
|
||||
showingTerminal: state.get('controlPipes').size > 0,
|
||||
urlState: getUrlState(state)
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { selectNetwork, showNetworks } from '../actions/app-actions';
|
||||
import { availableNetworksSelector } from '../selectors/node-networks';
|
||||
import NetworkSelectorItem from './network-selector-item';
|
||||
|
||||
class NetworkSelector extends React.Component {
|
||||
@@ -51,7 +52,7 @@ class NetworkSelector extends React.Component {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
availableNetworks: state.get('availableNetworks'),
|
||||
availableNetworks: availableNetworksSelector(state),
|
||||
showingNetworks: state.get('showingNetworks'),
|
||||
pinnedNetwork: state.get('pinnedNetwork')
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fromJS, is as isDeepEqual, List as makeList, Map as makeMap,
|
||||
import ActionTypes from '../constants/action-types';
|
||||
import { EDGE_ID_SEPARATOR } from '../constants/naming';
|
||||
import { applyPinnedSearches } from '../utils/search-utils';
|
||||
import { getNetworkNodes, getAvailableNetworks } from '../utils/network-view-utils';
|
||||
import { getNetworkNodes } from '../utils/network-view-utils';
|
||||
import {
|
||||
findTopologyById,
|
||||
getAdjacentNodes,
|
||||
@@ -29,7 +29,6 @@ const topologySorter = topology => topology.get('rank');
|
||||
|
||||
export const initialState = makeMap({
|
||||
availableCanvasMetrics: makeList(),
|
||||
availableNetworks: makeList(),
|
||||
controlPipes: makeOrderedMap(), // pipeId -> controlPipe
|
||||
controlStatus: makeMap(),
|
||||
currentTopology: null,
|
||||
@@ -572,23 +571,7 @@ export function rootReducer(state = initialState, action) {
|
||||
|
||||
// apply pinned searches, filters nodes that dont match
|
||||
state = applyPinnedSearches(state);
|
||||
|
||||
// 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) => {
|
||||
if (node.has('metadata')) {
|
||||
const networks = node.get('metadata')
|
||||
.find(field => field.get('id') === 'docker_container_networks');
|
||||
if (networks) {
|
||||
return node.set('networks', fromJS(
|
||||
networks.get('value').split(', ').map(n => ({id: n, label: n, colorKey: n}))));
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}));
|
||||
|
||||
state = state.set('networkNodes', getNetworkNodes(state.get('nodes')));
|
||||
state = state.set('availableNetworks', getAvailableNetworks(state.get('nodes')));
|
||||
state = state.set('networkNodes', getNetworkNodes(state));
|
||||
|
||||
state = state.set('availableCanvasMetrics', state.get('nodes')
|
||||
.valueSeq()
|
||||
|
||||
9
client/app/scripts/selectors/node-filters.js
Normal file
9
client/app/scripts/selectors/node-filters.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
|
||||
export const shownNodesSelector = createSelector(
|
||||
[
|
||||
state => state.get('nodes'),
|
||||
],
|
||||
nodes => nodes.filter(node => !node.get('filtered'))
|
||||
);
|
||||
@@ -10,7 +10,7 @@ const topCardNodeSelector = createSelector(
|
||||
nodeDetails => nodeDetails.last()
|
||||
);
|
||||
|
||||
export const nodeMetricsSelector = createMapSelector(
|
||||
export const nodeMetricSelector = createMapSelector(
|
||||
[
|
||||
state => state.get('nodes'),
|
||||
state => state.get('selectedMetric'),
|
||||
45
client/app/scripts/selectors/node-networks.js
Normal file
45
client/app/scripts/selectors/node-networks.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { createMapSelector } from 'reselect-map';
|
||||
import { fromJS, List as makeList } from 'immutable';
|
||||
|
||||
|
||||
const extractNodeNetworksValue = (node) => {
|
||||
if (node.has('metadata')) {
|
||||
const networks = node.get('metadata')
|
||||
.find(field => field.get('id') === 'docker_container_networks');
|
||||
return networks && networks.get('value');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 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.
|
||||
export const nodeNetworksSelector = createMapSelector(
|
||||
[
|
||||
state => state.get('nodes').map(extractNodeNetworksValue),
|
||||
],
|
||||
(networksValue) => {
|
||||
if (!networksValue) {
|
||||
return makeList();
|
||||
}
|
||||
return fromJS(networksValue.split(', ').map(network => ({
|
||||
id: network, label: network, colorKey: network
|
||||
})));
|
||||
}
|
||||
);
|
||||
|
||||
export const availableNetworksSelector = createSelector(
|
||||
[
|
||||
nodeNetworksSelector
|
||||
],
|
||||
networksMap => networksMap.toList().flatten(true).toSet().toList()
|
||||
.sortBy(m => m.get('label'))
|
||||
);
|
||||
|
||||
export const selectedNetworkNodesIdsSelector = createSelector(
|
||||
[
|
||||
state => state.get('networkNodes'),
|
||||
state => state.get('selectedNetwork'),
|
||||
],
|
||||
(networkNodes, selectedNetwork) => networkNodes.get(selectedNetwork)
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { Map as makeMap } from 'immutable';
|
||||
|
||||
|
||||
export const shownNodesSelector = createSelector(
|
||||
[
|
||||
state => state.get('nodes'),
|
||||
],
|
||||
nodes => nodes.filter(node => !node.get('filtered'))
|
||||
);
|
||||
|
||||
export const nodeAdjacenciesSelector = createSelector(
|
||||
[
|
||||
shownNodesSelector,
|
||||
],
|
||||
nodes => nodes.map(node => makeMap({
|
||||
id: node.get('id'),
|
||||
adjacency: node.get('adjacency'),
|
||||
label: node.get('label'),
|
||||
pseudo: node.get('pseudo'),
|
||||
subLabel: node.get('labelMinor'),
|
||||
metrics: node.get('metrics'),
|
||||
rank: node.get('rank'),
|
||||
shape: node.get('shape'),
|
||||
stack: node.get('stack'),
|
||||
networks: node.get('networks'),
|
||||
}))
|
||||
);
|
||||
@@ -18,7 +18,7 @@ export const searchNodeMatchesSelector = createMapSelector(
|
||||
parsedSearchQuerySelector,
|
||||
],
|
||||
// TODO: Bring map selectors one level deeper here so that `searchTopology` is
|
||||
// not executed against all the topology nodes every time a small change occurs.
|
||||
// not executed against all the topology nodes when the nodes delta is small.
|
||||
(nodes, parsed) => (parsed ? searchTopology(nodes, parsed) : makeMap())
|
||||
);
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { fromJS, List as makeList } from 'immutable';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
export function getNetworkNodes(nodes) {
|
||||
const networks = {};
|
||||
nodes.forEach(node => (node.get('networks') || makeList()).forEach((n) => {
|
||||
const networkId = n.get('id');
|
||||
networks[networkId] = (networks[networkId] || []).concat([node.get('id')]);
|
||||
}));
|
||||
return fromJS(networks);
|
||||
}
|
||||
|
||||
|
||||
export function getAvailableNetworks(nodes) {
|
||||
return nodes
|
||||
.valueSeq()
|
||||
.flatMap(node => node.get('networks') || makeList())
|
||||
.toSet()
|
||||
.toList()
|
||||
.sortBy(m => m.get('label'));
|
||||
import { nodeNetworksSelector } from '../selectors/node-networks';
|
||||
|
||||
export function getNetworkNodes(state) {
|
||||
const networksMap = {};
|
||||
nodeNetworksSelector(state).forEach((networks, nodeId) => {
|
||||
networks.forEach((network) => {
|
||||
const networkId = network.get('id');
|
||||
networksMap[networkId] = networksMap[networkId] || [];
|
||||
networksMap[networkId].push(nodeId);
|
||||
});
|
||||
});
|
||||
return fromJS(networksMap);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user