mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
If the scope-app API unexpectedly restarts, it has no report at hand (until it gets one from the probe) and sends node count 0 to the frontend for all topologies. Once the report arrives, it will send the proper count. What happened was the frontend did hide Processes for a short time till the node count recovered. This moved the topology selection to the always visible Containers (hide_if_empty == false) while keeping the graph as is. Once the node count recovers, Processes comes back but the selection is still at Containers. We now keep the selected topology visible at all time even if the API returns a node count of 0. This recovers nicely when the correct node counts come in. Once the user selects a different topology while and a backend response arrives, it disappears. Fixes #2646
181 lines
5.3 KiB
JavaScript
181 lines
5.3 KiB
JavaScript
import { endsWith } from 'lodash';
|
|
import { Set as makeSet, List as makeList } from 'immutable';
|
|
|
|
import { isPausedSelector } from '../selectors/time-travel';
|
|
import { isResourceViewModeSelector } from '../selectors/topology';
|
|
import { pinnedMetricSelector } from '../selectors/node-metric';
|
|
import { shownNodesSelector, shownResourceTopologyIdsSelector } from '../selectors/node-filters';
|
|
|
|
//
|
|
// top priority first
|
|
//
|
|
const TOPOLOGY_DISPLAY_PRIORITY = [
|
|
'ecs-services',
|
|
'ecs-tasks',
|
|
'kube-controllers',
|
|
'services',
|
|
'replica-sets',
|
|
'pods',
|
|
'containers',
|
|
];
|
|
|
|
|
|
export function getDefaultTopology(topologies) {
|
|
const flatTopologies = topologies
|
|
.flatMap(t => makeList([t]).concat(t.get('sub_topologies', makeList())));
|
|
|
|
return flatTopologies
|
|
.sortBy((t) => {
|
|
const index = TOPOLOGY_DISPLAY_PRIORITY.indexOf(t.get('id'));
|
|
return index === -1 ? Infinity : index;
|
|
})
|
|
.getIn([0, 'id']);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a cache ID based on the topologyId and optionsQuery
|
|
* @param {String} topologyId
|
|
* @param {object} topologyOptions (optional)
|
|
* @return {String}
|
|
*/
|
|
export function buildTopologyCacheId(topologyId, topologyOptions) {
|
|
let id = '';
|
|
if (topologyId) {
|
|
id = topologyId;
|
|
if (topologyOptions) {
|
|
id += JSON.stringify(topologyOptions);
|
|
}
|
|
}
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Returns a topology object from the topology tree
|
|
* @param {List} subTree
|
|
* @param {String} topologyId
|
|
* @return {Map} topology if found
|
|
*/
|
|
export function findTopologyById(subTree, topologyId) {
|
|
let foundTopology;
|
|
|
|
subTree.forEach((topology) => {
|
|
if (endsWith(topology.get('url'), topologyId)) {
|
|
foundTopology = topology;
|
|
}
|
|
if (!foundTopology && topology.has('sub_topologies')) {
|
|
foundTopology = findTopologyById(topology.get('sub_topologies'), topologyId);
|
|
}
|
|
});
|
|
|
|
return foundTopology;
|
|
}
|
|
|
|
export function updateNodeDegrees(nodes, edges) {
|
|
return nodes.map((node) => {
|
|
const nodeId = node.get('id');
|
|
const degree = edges.count(edge => edge.get('source') === nodeId
|
|
|| edge.get('target') === nodeId);
|
|
return node.set('degree', degree);
|
|
});
|
|
}
|
|
|
|
/* set topology.id and parentId for sub-topologies in place */
|
|
export function updateTopologyIds(topologies, parentId) {
|
|
return topologies.map((topology) => {
|
|
const result = Object.assign({}, topology);
|
|
result.id = topology.url.split('/').pop();
|
|
if (parentId) {
|
|
result.parentId = parentId;
|
|
}
|
|
if (topology.sub_topologies) {
|
|
result.sub_topologies = updateTopologyIds(topology.sub_topologies, result.id);
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
export function addTopologyFullname(topologies) {
|
|
return topologies.map((t) => {
|
|
if (!t.sub_topologies) {
|
|
return Object.assign({}, t, {fullName: t.name});
|
|
}
|
|
return Object.assign({}, t, {
|
|
fullName: t.name,
|
|
sub_topologies: t.sub_topologies.map(st => (
|
|
Object.assign({}, st, {fullName: `${t.name} ${st.name}`})
|
|
))
|
|
});
|
|
});
|
|
}
|
|
|
|
// adds ID field to topology (based on last part of URL path) and save urls in
|
|
// map for easy lookup
|
|
export function setTopologyUrlsById(topologyUrlsById, topologies) {
|
|
let urlMap = topologyUrlsById;
|
|
if (topologies) {
|
|
topologies.forEach((topology) => {
|
|
urlMap = urlMap.set(topology.id, topology.url);
|
|
if (topology.sub_topologies) {
|
|
topology.sub_topologies.forEach((subTopology) => {
|
|
urlMap = urlMap.set(subTopology.id, subTopology.url);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return urlMap;
|
|
}
|
|
|
|
export function filterHiddenTopologies(topologies, currentTopologyId) {
|
|
return topologies.filter(t => (!t.hide_if_empty || t.stats.node_count > 0 ||
|
|
t.stats.filtered_nodes > 0 || t.id === currentTopologyId));
|
|
}
|
|
|
|
export function getCurrentTopologyOptions(state) {
|
|
return state.getIn(['currentTopology', 'options']);
|
|
}
|
|
|
|
export function isTopologyNodeCountZero(state) {
|
|
const nodeCount = state.getIn(['currentTopology', 'stats', 'node_count'], 0);
|
|
// If we are browsing the past, assume there would normally be some nodes at different times.
|
|
// If we are in the resource view, don't rely on these stats at all (for now).
|
|
return nodeCount === 0 && !isPausedSelector(state) && !isResourceViewModeSelector(state);
|
|
}
|
|
|
|
export function isNodesDisplayEmpty(state) {
|
|
// Consider a topology in the resource view empty if it has no pinned metric.
|
|
if (isResourceViewModeSelector(state)) {
|
|
return !pinnedMetricSelector(state) || shownResourceTopologyIdsSelector(state).isEmpty();
|
|
}
|
|
// Otherwise (in graph and table view), we only look at the nodes content.
|
|
return shownNodesSelector(state).isEmpty();
|
|
}
|
|
|
|
export function getAdjacentNodes(state, originNodeId) {
|
|
let adjacentNodes = makeSet();
|
|
const nodeId = originNodeId || state.get('selectedNodeId');
|
|
|
|
if (nodeId) {
|
|
if (state.hasIn(['nodes', nodeId])) {
|
|
adjacentNodes = makeSet(state.getIn(['nodes', nodeId, 'adjacency']));
|
|
// fill up set with reverse edges
|
|
state.get('nodes').forEach((node, id) => {
|
|
if (node.get('adjacency') && node.get('adjacency').includes(nodeId)) {
|
|
adjacentNodes = adjacentNodes.add(id);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return adjacentNodes;
|
|
}
|
|
|
|
export function hasSelectedNode(state) {
|
|
const selectedNodeId = state.get('selectedNodeId');
|
|
return state.hasIn(['nodes', selectedNodeId]);
|
|
}
|
|
|
|
export function getCurrentTopologyUrl(state) {
|
|
return state.getIn(['currentTopology', 'url']);
|
|
}
|