Merge pull request #2807 from weaveworks/keep-node-details-up-to-date-when-time-travelling

Time Travel: keep active node details panels up-to-date
This commit is contained in:
Filip Barl
2017-08-14 10:03:59 +02:00
committed by GitHub
6 changed files with 53 additions and 23 deletions

View File

@@ -228,6 +228,8 @@ 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);
};
}
@@ -538,9 +540,10 @@ export function receiveControlSuccess(nodeId) {
};
}
export function receiveNodeDetails(details) {
export function receiveNodeDetails(details, requestTimestamp) {
return {
type: ActionTypes.RECEIVE_NODE_DETAILS,
requestTimestamp,
details
};
}
@@ -598,6 +601,9 @@ export function startTimeTravel() {
if (isResourceViewModeSelector(getState())) {
getResourceViewNodesSnapshot(getState(), dispatch);
}
} else {
// Get most recent details before freezing the state.
getNodeDetails(getState, dispatch);
}
};
}
@@ -731,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
};
}

View File

@@ -8,7 +8,9 @@ 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';
import NodeDetailsControls from './node-details/node-details-controls';
import NodeDetailsGenericTable from './node-details/node-details-generic-table';
@@ -67,7 +69,11 @@ class NodeDetails extends React.Component {
onClick={this.handleShowTopologyForNode}>
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
</span>}
<span title="Close details" className="fa fa-close" onClick={this.handleClickClose} />
<span
title="Close details"
className="fa fa-close close-details"
onClick={this.handleClickClose}
/>
</div>
</div>
);
@@ -134,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>
);
}
@@ -241,6 +248,8 @@ class NodeDetails extends React.Component {
/>
</CloudFeature>
</div>
<Overlay faded={this.props.transitioning} />
</div>
);
}
@@ -280,6 +289,7 @@ 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]),
nodes: state.get('nodes'),
selectedNodeId: state.get('selectedNodeId'),

View File

@@ -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,12 +554,11 @@ export function rootReducer(state = initialState, action) {
// disregard if node is not selected anymore
if (state.hasIn(['nodeDetails', action.details.id])) {
state = state.updateIn(['nodeDetails', action.details.id], (obj) => {
const result = Object.assign({}, obj);
result.notFound = false;
result.details = action.details;
return result;
});
state = state.updateIn(['nodeDetails', action.details.id], obj => ({ ...obj,
notFound: false,
timestamp: action.requestTimestamp,
details: action.details,
}));
}
return state;
}
@@ -627,11 +628,10 @@ export function rootReducer(state = initialState, action) {
case ActionTypes.RECEIVE_NOT_FOUND: {
if (state.hasIn(['nodeDetails', action.nodeId])) {
state = state.updateIn(['nodeDetails', action.nodeId], (obj) => {
const result = Object.assign({}, obj);
result.notFound = true;
return result;
});
state = state.updateIn(['nodeDetails', action.nodeId], obj => ({ ...obj,
timestamp: action.requestTimestamp,
notFound: true,
}));
}
return state;
}

View File

@@ -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;
}

View File

@@ -3,11 +3,13 @@ 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 } from '../actions/app-actions';
receiveNodesForTopology, receiveNodes,
} from '../actions/app-actions';
import { getCurrentTopologyUrl } from '../utils/topology-utils';
import { layersTopologyIdsSelector } from '../selectors/resource-view/layout';
@@ -282,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();
@@ -304,14 +307,14 @@ export function getNodeDetails(getState, dispatch) {
success: (res) => {
// make sure node is still selected
if (nodeMap.has(res.node.id)) {
dispatch(receiveNodeDetails(res.node));
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));
}

View File

@@ -78,7 +78,7 @@
left: 0;
opacity: 0;
pointer-events: none;
z-index: 2000;
z-index: 1000;
&.faded {
// NOTE: Not sure if we should block the pointer events here..
@@ -737,6 +737,10 @@
top: 6px;
right: 8px;
.close-details {
position: relative;
z-index: 1024;
}
> span {
@extend .btn-opacity;