import { endsWith } from 'lodash'; import { Set as makeSet, List as makeList, Map as makeMap } from 'immutable'; import stableStringify from 'json-stable-stringify'; 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 += stableStringify(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) { if(topologies) { return topologies.map((topology) => { if (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, currentTopology) { currentTopology = currentTopology || makeMap(); return topologies.filter(t => (!t.hide_if_empty || t.stats.node_count > 0 || t.stats.filtered_nodes > 0 || t.id === currentTopology.get('id') || t.id === currentTopology.get('parentId'))); } 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']); }