diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 2a2fb3f9f3..429124e8b8 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -8,13 +8,12 @@ import { doControlRequest, getAllNodes, getResourceViewNodesSnapshot, - updateWebsocketChannel, getNodeDetails, getTopologies, deletePipe, stopPolling, teardownWebsockets, - getNodesOnce, + getNodes, } from '../utils/web-api-utils'; import { storageSet } from '../utils/storage-utils'; import { loadTheme } from '../utils/contrast-utils'; @@ -40,15 +39,6 @@ import { const log = debug('scope:app-actions'); -function getNodes(getState, dispatch, justUnpaused = false) { - if (isPausedSelector(getState())) { - getNodesOnce(getState, dispatch); - } else { - updateWebsocketChannel(getState, dispatch, justUnpaused); - } - getNodeDetails(getState, dispatch); -} - export function showHelp() { return { type: ActionTypes.SHOW_HELP }; } @@ -346,7 +336,7 @@ export function clickNode(nodeId, label, origin) { }; } -export function clickPauseUpdate() { +export function pauseTimeAtNow() { return { type: ActionTypes.PAUSE_TIME_AT_NOW }; @@ -572,10 +562,10 @@ export function receiveNodesDelta(delta) { }; } -export function clickResumeUpdate() { +export function resumeTime() { return (dispatch, getState) => { dispatch({ - type: ActionTypes.RESUME_TIME_FROM_NOW + type: ActionTypes.RESUME_TIME }); // After unpausing, all of the following calls will re-activate polling. getTopologies(getState, dispatch); diff --git a/client/app/scripts/components/time-control.js b/client/app/scripts/components/time-control.js index db6488ced2..fd3680a8e2 100644 --- a/client/app/scripts/components/time-control.js +++ b/client/app/scripts/components/time-control.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import CloudFeature from './cloud-feature'; import TimeTravelButton from './time-travel-button'; import { trackMixpanelEvent } from '../utils/tracking-utils'; -import { clickPauseUpdate, clickResumeUpdate, clickTimeTravel } from '../actions/app-actions'; +import { pauseTimeAtNow, resumeTime, clickTimeTravel } from '../actions/app-actions'; const className = isSelected => ( @@ -34,12 +34,12 @@ class TimeControl extends React.Component { handleNowClick() { trackMixpanelEvent('scope.time.resume.click', this.getTrackingMetadata()); - this.props.clickResumeUpdate(); + this.props.resumeTime(); } handlePauseClick() { trackMixpanelEvent('scope.time.pause.click', this.getTrackingMetadata()); - this.props.clickPauseUpdate(); + this.props.pauseTimeAtNow(); } handleTravelClick() { @@ -115,8 +115,8 @@ function mapStateToProps(state) { export default connect( mapStateToProps, { - clickPauseUpdate, - clickResumeUpdate, + resumeTime, + pauseTimeAtNow, clickTimeTravel, } )(TimeControl); diff --git a/client/app/scripts/components/time-travel-button.js b/client/app/scripts/components/time-travel-button.js index 444e5c33f9..3bb1764819 100644 --- a/client/app/scripts/components/time-travel-button.js +++ b/client/app/scripts/components/time-travel-button.js @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; +// TODO: Move this back into TimeTravelControls once we move away from CloudFeature. class TimeTravelButton extends React.Component { render() { const { className, onClick, isTimeTravelling, hasTimeTravel } = this.props; diff --git a/client/app/scripts/components/time-travel.js b/client/app/scripts/components/time-travel.js index 0c1296d571..979913d799 100644 --- a/client/app/scripts/components/time-travel.js +++ b/client/app/scripts/components/time-travel.js @@ -8,7 +8,7 @@ import { debounce, map } from 'lodash'; import { trackMixpanelEvent } from '../utils/tracking-utils'; import { jumpToTime, - clickResumeUpdate, + resumeTime, timeTravelStartTransition, } from '../actions/app-actions'; @@ -65,7 +65,7 @@ class TimeTravel extends React.Component { componentWillUnmount() { clearInterval(this.timer); - this.props.clickResumeUpdate(); + this.props.resumeTime(); } handleSliderChange(timestamp) { @@ -123,6 +123,8 @@ class TimeTravel extends React.Component { renderMark({ timestampValue, label }) { const sliderMaxValue = moment().valueOf(); const pos = (sliderMaxValue - timestampValue) / (sliderMaxValue - this.state.sliderMinValue); + + // Ignore the month marks that are very close to 'Now' if (label !== 'Now' && pos < 0.05) return null; const style = { marginLeft: `calc(${(1 - pos) * 100}% - 32px)`, width: '64px' }; @@ -147,6 +149,7 @@ class TimeTravel extends React.Component { do { timestamp = moment().utc().subtract(monthsBack, 'months').startOf('month'); if (timestamp.valueOf() >= sliderMinValue) { + // Months are broken by the year tag, e.g. November, December, 2016, February, etc... let label = timestamp.format('MMMM'); if (label === 'January') { label = timestamp.format('YYYY'); @@ -215,7 +218,7 @@ export default connect( mapStateToProps, { jumpToTime, - clickResumeUpdate, + resumeTime, timeTravelStartTransition, } )(TimeTravel); diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index 088556152a..5238ad391f 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -12,13 +12,9 @@ const ACTION_TYPES = [ 'CLICK_CLOSE_TERMINAL', 'CLICK_FORCE_RELAYOUT', 'CLICK_NODE', - 'PAUSE_TIME_AT_NOW', 'CLICK_RELATIVE', - 'RESUME_TIME_FROM_NOW', - 'JUMP_TO_TIME', 'CLICK_SHOW_TOPOLOGY_FOR_NODE', 'CLICK_TERMINAL', - 'START_TIME_TRAVEL', 'CLICK_TOPOLOGY', 'CLOSE_WEBSOCKET', 'DEBUG_TOOLBAR_INTERFERING', @@ -33,9 +29,11 @@ const ACTION_TYPES = [ 'FOCUS_SEARCH', 'HIDE_HELP', 'HOVER_METRIC', + 'JUMP_TO_TIME', 'LEAVE_EDGE', 'LEAVE_NODE', 'OPEN_WEBSOCKET', + 'PAUSE_TIME_AT_NOW', 'PIN_METRIC', 'PIN_NETWORK', 'PIN_SEARCH', @@ -53,6 +51,7 @@ const ACTION_TYPES = [ 'RECEIVE_TOPOLOGIES', 'REQUEST_SERVICE_IMAGES', 'RESET_LOCAL_VIEW_STATE', + 'RESUME_TIME', 'ROUTE_TOPOLOGY', 'SELECT_NETWORK', 'SET_EXPORTING_GRAPH', @@ -63,6 +62,7 @@ const ACTION_TYPES = [ 'SHOW_NETWORKS', 'SHUTDOWN', 'SORT_ORDER_CHANGED', + 'START_TIME_TRAVEL', 'TIME_TRAVEL_START_TRANSITION', 'TOGGLE_CONTRAST_MODE', 'TOGGLE_TROUBLESHOOTING_MENU', diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index 891866fabd..24103c6b1d 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -363,7 +363,7 @@ export function rootReducer(state = initialState, action) { // time control // - case ActionTypes.RESUME_TIME_FROM_NOW: { + case ActionTypes.RESUME_TIME: { state = state.set('timeTravelTransitioning', true); state = state.set('showingTimeTravel', false); return state.set('pausedAt', null); diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js index b802b26584..88406b7099 100644 --- a/client/app/scripts/utils/web-api-utils.js +++ b/client/app/scripts/utils/web-api-utils.js @@ -197,7 +197,7 @@ function getNodesForTopologies(state, dispatch, topologyIds, topologyOptions = m Promise.resolve()); } -export function getNodesOnce(getState, dispatch) { +function getNodesOnce(getState, dispatch) { const state = getState(); const topologyUrl = getCurrentTopologyUrl(state); const topologyOptions = activeTopologyOptionsSelector(state); @@ -263,7 +263,7 @@ export function getTopologies(getState, dispatch, initialPoll = false) { }); } -export function updateWebsocketChannel(getState, dispatch, justUnpaused = false) { +function updateWebsocketChannel(getState, dispatch, forceRequest) { const topologyUrl = getCurrentTopologyUrl(getState()); const topologyOptions = activeTopologyOptionsSelector(getState()); const websocketUrl = buildWebsocketUrl(topologyUrl, topologyOptions, getState()); @@ -271,7 +271,7 @@ export function updateWebsocketChannel(getState, dispatch, justUnpaused = false) const isNewUrl = websocketUrl !== currentUrl; // `topologyUrl` can be undefined initially, so only create a socket if it is truthy // and no socket exists, or if we get a new url. - if (topologyUrl && (!socket || isNewUrl || justUnpaused)) { + if (topologyUrl && (!socket || isNewUrl || forceRequest)) { createWebsocket(websocketUrl, getState, dispatch); currentUrl = websocketUrl; } @@ -318,6 +318,15 @@ export function getNodeDetails(getState, dispatch) { } } +export function getNodes(getState, dispatch, forceRequest = false) { + if (isPausedSelector(getState())) { + getNodesOnce(getState, dispatch); + } else { + updateWebsocketChannel(getState, dispatch, forceRequest); + } + getNodeDetails(getState, dispatch); +} + export function getApiDetails(dispatch) { clearTimeout(apiDetailsTimer); const url = `${getApiPath()}/api`; diff --git a/client/app/styles/_base.scss b/client/app/styles/_base.scss index eabb74e91c..bcc62fcf98 100644 --- a/client/app/styles/_base.scss +++ b/client/app/styles/_base.scss @@ -1958,7 +1958,7 @@ // .nodes-grid { - // TODO: Use relative positioning here. + // TODO: Would be good to have relative positioning here. position: absolute; top: 0;