mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Smarter node details transition and polishing the edge cases.
This commit is contained in:
@@ -228,6 +228,7 @@ export function clickCloseDetails(nodeId) {
|
||||
type: ActionTypes.CLICK_CLOSE_DETAILS,
|
||||
nodeId
|
||||
});
|
||||
// Pull the most recent details for the next details panel that comes into focus.
|
||||
getNodeDetails(getState, dispatch);
|
||||
updateRoute(getState);
|
||||
};
|
||||
@@ -539,20 +540,14 @@ export function receiveControlSuccess(nodeId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveNodeDetails(details, timestamp = null) {
|
||||
export function receiveNodeDetails(details, requestTimestamp) {
|
||||
return {
|
||||
type: ActionTypes.RECEIVE_NODE_DETAILS,
|
||||
timestamp,
|
||||
requestTimestamp,
|
||||
details
|
||||
};
|
||||
}
|
||||
|
||||
export function nodeDetailsStartTransition() {
|
||||
return {
|
||||
type: ActionTypes.NODE_DETAILS_START_TRANSITION
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveNodesDelta(delta) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isPausedSelector(getState())) {
|
||||
@@ -606,6 +601,9 @@ export function startTimeTravel() {
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
} else {
|
||||
// Get most recent details before freezing the state.
|
||||
getNodeDetails(getState, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -739,10 +737,11 @@ export function receiveError(errorUrl) {
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveNotFound(nodeId) {
|
||||
export function receiveNotFound(nodeId, requestTimestamp) {
|
||||
return {
|
||||
type: ActionTypes.RECEIVE_NOT_FOUND,
|
||||
requestTimestamp,
|
||||
nodeId,
|
||||
type: ActionTypes.RECEIVE_NOT_FOUND
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { clickCloseDetails, clickShowTopologyForNode } from '../actions/app-acti
|
||||
import { brightenColor, getNeutralColor, getNodeColorDark } from '../utils/color-utils';
|
||||
import { isGenericTable, isPropertyList } from '../utils/node-details-utils';
|
||||
import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils';
|
||||
import { timestampsEqual } from '../utils/time-utils';
|
||||
|
||||
import Overlay from './overlay';
|
||||
import MatchedText from './matched-text';
|
||||
@@ -139,6 +140,7 @@ class NodeDetails extends React.Component {
|
||||
Details will become available here when it communicates again.
|
||||
</p>
|
||||
</div>
|
||||
<Overlay faded={this.props.transitioning} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -156,7 +158,7 @@ class NodeDetails extends React.Component {
|
||||
}
|
||||
|
||||
renderDetails() {
|
||||
const { details, nodeControlStatus, transitioning, nodeMatches = makeMap() } = this.props;
|
||||
const { details, nodeControlStatus, nodeMatches = makeMap() } = this.props;
|
||||
const showControls = details.controls && details.controls.length > 0;
|
||||
const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
|
||||
const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
|
||||
@@ -247,7 +249,7 @@ class NodeDetails extends React.Component {
|
||||
</CloudFeature>
|
||||
</div>
|
||||
|
||||
<Overlay faded={transitioning} />
|
||||
<Overlay faded={this.props.transitioning} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -287,8 +289,8 @@ class NodeDetails extends React.Component {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const currentTopologyId = state.get('currentTopologyId');
|
||||
return {
|
||||
transitioning: !timestampsEqual(state.get('pausedAt'), ownProps.timestamp),
|
||||
nodeMatches: state.getIn(['searchNodeMatches', currentTopologyId, ownProps.id]),
|
||||
transitioning: state.get('pausedAt') && (!ownProps.timestamp || ownProps.timestamp.toISOString() !== state.get('pausedAt').toISOString()),
|
||||
nodes: state.get('nodes'),
|
||||
selectedNodeId: state.get('selectedNodeId'),
|
||||
};
|
||||
|
||||
@@ -62,7 +62,6 @@ const ACTION_TYPES = [
|
||||
'SHOW_NETWORKS',
|
||||
'SHUTDOWN',
|
||||
'SORT_ORDER_CHANGED',
|
||||
'NODE_DETAILS_START_TRANSITION',
|
||||
'START_TIME_TRAVEL',
|
||||
'TIME_TRAVEL_START_TRANSITION',
|
||||
'TOGGLE_CONTRAST_MODE',
|
||||
|
||||
@@ -18,8 +18,9 @@ import {
|
||||
graphExceedsComplexityThreshSelector,
|
||||
isResourceViewModeSelector,
|
||||
} from '../selectors/topology';
|
||||
import { isPausedSelector } from '../selectors/time-travel';
|
||||
import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming';
|
||||
import { nowInSecondsPrecision } from '../utils/time-utils';
|
||||
import { nowInSecondsPrecision, timestampsEqual } from '../utils/time-utils';
|
||||
import { applyPinnedSearches } from '../utils/search-utils';
|
||||
import {
|
||||
findTopologyById,
|
||||
@@ -543,8 +544,9 @@ export function rootReducer(state = initialState, action) {
|
||||
}
|
||||
|
||||
case ActionTypes.RECEIVE_NODE_DETAILS: {
|
||||
// Freeze node details data updates after the first load when paused.
|
||||
if (state.getIn(['nodeDetails', action.details.id, 'details']) && state.get('pausedAt')) {
|
||||
// Ignore the update if paused and the timestamp didn't change.
|
||||
const setTimestamp = state.getIn(['nodeDetails', action.details.id, 'timestamp']);
|
||||
if (isPausedSelector(state) && timestampsEqual(action.requestTimestamp, setTimestamp)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -552,26 +554,15 @@ export function rootReducer(state = initialState, action) {
|
||||
|
||||
// disregard if node is not selected anymore
|
||||
if (state.hasIn(['nodeDetails', action.details.id])) {
|
||||
console.log(action.timestamp && action.timestamp.toISOString());
|
||||
state = state.updateIn(['nodeDetails', action.details.id], obj => ({ ...obj,
|
||||
notFound: false,
|
||||
timestamp: action.timestamp,
|
||||
timestamp: action.requestTimestamp,
|
||||
details: action.details,
|
||||
}));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
case ActionTypes.NODE_DETAILS_START_TRANSITION: {
|
||||
const topNode = state.get('nodeDetails').last();
|
||||
if (topNode && topNode.id) {
|
||||
state = state.updateIn(['nodeDetails', topNode.id], obj => ({ ...obj,
|
||||
transitioning: true,
|
||||
}));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
case ActionTypes.SET_RECEIVED_NODES_DELTA: {
|
||||
// Turn on the table view if the graph is too complex, but skip
|
||||
// this block if the user has already loaded topologies once.
|
||||
@@ -638,6 +629,7 @@ export function rootReducer(state = initialState, action) {
|
||||
case ActionTypes.RECEIVE_NOT_FOUND: {
|
||||
if (state.hasIn(['nodeDetails', action.nodeId])) {
|
||||
state = state.updateIn(['nodeDetails', action.nodeId], obj => ({ ...obj,
|
||||
timestamp: action.requestTimestamp,
|
||||
notFound: true,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -24,3 +24,9 @@ export function clampToNowInSecondsPrecision(timestamp) {
|
||||
export function scaleDuration(duration, scale) {
|
||||
return moment.duration(duration.asMilliseconds() * scale);
|
||||
}
|
||||
|
||||
export function timestampsEqual(timestampA, timestampB) {
|
||||
const stringifiedTimestampA = timestampA ? timestampA.toISOString() : '';
|
||||
const stringifiedTimestampB = timestampB ? timestampB.toISOString() : '';
|
||||
return stringifiedTimestampA === stringifiedTimestampB;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import reqwest from 'reqwest';
|
||||
import { defaults } from 'lodash';
|
||||
import { Map as makeMap, List } from 'immutable';
|
||||
|
||||
import { blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
|
||||
import {
|
||||
blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
|
||||
receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
|
||||
receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
|
||||
receiveControlSuccess, receiveTopologies, receiveNotFound,
|
||||
receiveNodesForTopology, receiveNodes, nodeDetailsStartTransition
|
||||
receiveNodesForTopology, receiveNodes,
|
||||
} from '../actions/app-actions';
|
||||
|
||||
import { getCurrentTopologyUrl } from '../utils/topology-utils';
|
||||
@@ -283,6 +284,7 @@ export function getNodeDetails(getState, dispatch) {
|
||||
const nodeMap = state.get('nodeDetails');
|
||||
const topologyUrlsById = state.get('topologyUrlsById');
|
||||
const currentTopologyId = state.get('currentTopologyId');
|
||||
const requestTimestamp = state.get('pausedAt');
|
||||
|
||||
// get details for all opened nodes
|
||||
const obj = nodeMap.last();
|
||||
@@ -294,29 +296,25 @@ export function getNodeDetails(getState, dispatch) {
|
||||
const topologyOptions = currentTopologyId === obj.topologyId
|
||||
? activeTopologyOptionsSelector(state) : makeMap();
|
||||
|
||||
const timestamp = state.get('pausedAt');
|
||||
const query = buildUrlQuery(topologyOptions, state);
|
||||
if (query) {
|
||||
urlComponents = urlComponents.concat(['?', query]);
|
||||
}
|
||||
const url = urlComponents.join('');
|
||||
|
||||
// if (isPausedSelector(state)) {
|
||||
// dispatch(nodeDetailsStartTransition());
|
||||
// }
|
||||
doRequest({
|
||||
url,
|
||||
success: (res) => {
|
||||
// make sure node is still selected
|
||||
if (nodeMap.has(res.node.id)) {
|
||||
dispatch(receiveNodeDetails(res.node, timestamp));
|
||||
dispatch(receiveNodeDetails(res.node, requestTimestamp));
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
log(`Error in node details request: ${err.responseText}`);
|
||||
// dont treat missing node as error
|
||||
if (err.status === 404) {
|
||||
dispatch(receiveNotFound(obj.id));
|
||||
dispatch(receiveNotFound(obj.id, requestTimestamp));
|
||||
} else {
|
||||
dispatch(receiveError(topologyUrl));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user