mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
import debug from 'debug';
|
|
import { identity } from 'lodash';
|
|
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
|
|
import { Map as makeMap, is, Set } from 'immutable';
|
|
|
|
import { getAdjacentNodes } from '../utils/topology-utils';
|
|
import { getSearchableFields } from '../utils/search-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 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('labelMinor'),
|
|
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'),
|
|
}))
|
|
);
|
|
|
|
|
|
export const searchableFieldsSelector = returnPreviousRefIfEqual(
|
|
createSelector(
|
|
allNodesSelector,
|
|
getSearchableFields
|
|
)
|
|
);
|
|
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
);
|