Skip to content

Commit

Permalink
Merge pull request #3065 from weaveworks/always-show-time-travel
Browse files Browse the repository at this point in the history
Show Time Travel at all times in Weave Cloud
  • Loading branch information
fbarl authored Feb 13, 2018
2 parents 14f77fc + dcb193d commit 2101a28
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 102 deletions.
29 changes: 16 additions & 13 deletions client/app/scripts/actions/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,15 @@ export function clickNode(nodeId, label, origin, topologyId = null) {

export function pauseTimeAtNow() {
return (dispatch, getState) => {
const getScopeState = () => getState().scope || getState();
dispatch({
type: ActionTypes.PAUSE_TIME_AT_NOW
});
updateRoute(getState);
if (!getState().get('nodesLoaded')) {
getNodes(getState, dispatch);
if (isResourceViewModeSelector(getState())) {
getResourceViewNodesSnapshot(getState(), dispatch);
updateRoute(getScopeState);
if (!getScopeState().get('nodesLoaded')) {
getNodes(getScopeState, dispatch);
if (isResourceViewModeSelector(getScopeState())) {
getResourceViewNodesSnapshot(getScopeState(), dispatch);
}
}
};
Expand Down Expand Up @@ -549,7 +550,8 @@ export function receiveNodeDetails(details, requestTimestamp) {

export function receiveNodesDelta(delta) {
return (dispatch, getState) => {
if (!isPausedSelector(getState())) {
const getScopeState = () => getState().scope || getState();
if (!isPausedSelector(getScopeState())) {
// Allow css-animation to run smoothly by scheduling it to run on the
// next tick after any potentially expensive canvas re-draws have been
// completed.
Expand All @@ -559,7 +561,7 @@ export function receiveNodesDelta(delta) {
// only when the first batch of nodes delta has been received. We
// do that because we want to keep the previous state blurred instead
// of transitioning over an empty state like when switching topologies.
if (getState().get('timeTravelTransitioning')) {
if (getScopeState().get('timeTravelTransitioning')) {
dispatch({ type: ActionTypes.FINISH_TIME_TRAVEL_TRANSITION });
}

Expand All @@ -576,16 +578,17 @@ export function receiveNodesDelta(delta) {

export function resumeTime() {
return (dispatch, getState) => {
if (isPausedSelector(getState())) {
const getScopeState = () => getState().scope || getState();
if (isPausedSelector(getScopeState())) {
dispatch({
type: ActionTypes.RESUME_TIME
});
updateRoute(getState);
updateRoute(getScopeState);
// After unpausing, all of the following calls will re-activate polling.
getTopologies(getState, dispatch);
getNodes(getState, dispatch, true);
if (isResourceViewModeSelector(getState())) {
getResourceViewNodesSnapshot(getState(), dispatch);
getTopologies(getScopeState, dispatch);
getNodes(getScopeState, dispatch, true);
if (isResourceViewModeSelector(getScopeState())) {
getResourceViewNodesSnapshot(getScopeState(), dispatch);
}
}
};
Expand Down
15 changes: 9 additions & 6 deletions client/app/scripts/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-t
import { getRouter, getUrlState } from '../utils/router-utils';
import { trackAnalyticsEvent } from '../utils/tracking-utils';
import { availableNetworksSelector } from '../selectors/node-networks';
import { timeTravelSupportedSelector } from '../selectors/time-travel';
import {
isResourceViewModeSelector,
isTableViewModeSelector,
Expand Down Expand Up @@ -177,10 +178,10 @@ class App extends React.Component {
const {
isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails,
showingHelp, showingNetworkSelector, showingTroubleshootingMenu,
timeTravelTransitioning, showingTimeTravel
timeTravelTransitioning, timeTravelSupported
} = this.props;

const className = classNames('scope-app', { 'time-travel-open': showingTimeTravel });
const className = classNames('scope-app', { 'time-travel-open': timeTravelSupported });
const isIframe = window !== window.top;

return (
Expand All @@ -195,9 +196,11 @@ class App extends React.Component {
{showingDetails && <Details />}

<div className="header">
<CloudFeature alwaysShow>
<TimeTravelWrapper />
</CloudFeature>
{timeTravelSupported && (
<CloudFeature alwaysShow>
<TimeTravelWrapper />
</CloudFeature>
)}

<div className="selectors">
<div className="logo">
Expand Down Expand Up @@ -244,11 +247,11 @@ function mapStateToProps(state) {
searchQuery: state.get('searchQuery'),
showingDetails: state.get('nodeDetails').size > 0,
showingHelp: state.get('showingHelp'),
showingTimeTravel: state.get('showingTimeTravel'),
showingTroubleshootingMenu: state.get('showingTroubleshootingMenu'),
showingNetworkSelector: availableNetworksSelector(state).count() > 0,
showingTerminal: state.get('controlPipes').size > 0,
topologyViewMode: state.get('topologyViewMode'),
timeTravelSupported: timeTravelSupportedSelector(state),
timeTravelTransitioning: state.get('timeTravelTransitioning'),
urlState: getUrlState(state)
};
Expand Down
1 change: 0 additions & 1 deletion client/app/scripts/components/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ function mapStateToProps(state) {
topologyNodeCountZero: isTopologyNodeCountZero(state),
nodesDisplayEmpty: isNodesDisplayEmpty(state),
nodesLoaded: nodesLoadedSelector(state),
timeTravelTransitioning: state.get('timeTravelTransitioning'),
currentTopology: state.get('currentTopology'),
topologies: state.get('topologies'),
topologiesLoaded: state.get('topologiesLoaded'),
Expand Down
67 changes: 19 additions & 48 deletions client/app/scripts/components/time-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import classNames from 'classnames';
import { connect } from 'react-redux';

import { trackAnalyticsEvent } from '../utils/tracking-utils';
import { pauseTimeAtNow, resumeTime, startTimeTravel } from '../actions/app-actions';
import { pauseTimeAtNow, resumeTime } from '../actions/app-actions';
import { isPausedSelector, timeTravelSupportedSelector } from '../selectors/time-travel';


const className = isSelected => (
Expand All @@ -17,7 +18,6 @@ class TimeControl extends React.Component {

this.handleNowClick = this.handleNowClick.bind(this);
this.handlePauseClick = this.handlePauseClick.bind(this);
this.handleTravelClick = this.handleTravelClick.bind(this);
this.getTrackingMetadata = this.getTrackingMetadata.bind(this);
}

Expand Down Expand Up @@ -50,84 +50,56 @@ class TimeControl extends React.Component {
this.props.pauseTimeAtNow();
}

handleTravelClick() {
if (!this.props.showingTimeTravel) {
trackAnalyticsEvent('scope.time.travel.click', this.getTrackingMetadata({ open: true }));
this.props.startTimeTravel();
} else {
trackAnalyticsEvent('scope.time.travel.click', this.getTrackingMetadata({ open: false }));
this.props.resumeTime();
}
}

render() {
const {
showingTimeTravel, pausedAt, timeTravelTransitioning, topologiesLoaded,
hasHistoricReports
} = this.props;

const isPausedNow = pausedAt && !showingTimeTravel;
const isTimeTravelling = showingTimeTravel;
const isRunningNow = !pausedAt;
const { isPaused, pausedAt, topologiesLoaded } = this.props;

if (!topologiesLoaded) return null;
// If Time Travel is supported, show an empty placeholder div instead
// of this control, since time will be controlled through the timeline.
// We return <div /> instead of null so that selector controls would
// be aligned the same way between WC Explore and Scope standalone.
if (this.props.timeTravelSupported) return <div />;

return (
<div className="time-control">
<div className="time-control-controls">
<div className="time-control-spinner">
{timeTravelTransitioning && <span className="fa fa-circle-o-notch fa-spin" />}
</div>
<div className="time-control-wrapper">
<span
className={className(isRunningNow)}
className={className(!isPaused)}
onClick={this.handleNowClick}
disabled={!topologiesLoaded}
title="Show live state of the system">
{isRunningNow && <span className="fa fa-play" />}
{!isPaused && <span className="fa fa-play" />}
<span className="label">Live</span>
</span>
<span
className={className(isPausedNow)}
onClick={!isTimeTravelling ? this.handlePauseClick : null}
disabled={isTimeTravelling}
className={className(isPaused)}
onClick={this.handlePauseClick}
disabled={!topologiesLoaded}
title="Pause updates (freezes the nodes in their current layout)">
{isPausedNow && <span className="fa fa-pause" />}
<span className="label">{isPausedNow ? 'Paused' : 'Pause'}</span>
{isPaused && <span className="fa fa-pause" />}
<span className="label">{isPaused ? 'Paused' : 'Pause'}</span>
</span>
{hasHistoricReports &&
<span
className={className(isTimeTravelling)}
onClick={this.handleTravelClick}
title="Travel back in time">
{isTimeTravelling && <span className="fa fa-clock-o" />}
<span className="label">Time Travel</span>
</span>
}
</div>
</div>
{(isPausedNow || isTimeTravelling) &&
{isPaused &&
<span
className="time-control-info"
title={moment(pausedAt).toISOString()}>
Showing state from {moment(pausedAt).fromNow()}
</span>
}
{isRunningNow && timeTravelTransitioning &&
<span className="time-control-info">Resuming the live state</span>
}
</div>
);
}
}

function mapStateToProps(state) {
return {
hasHistoricReports: state.getIn(['capabilities', 'historic_reports']),
isPaused: isPausedSelector(state),
timeTravelSupported: timeTravelSupportedSelector(state),
topologyViewMode: state.get('topologyViewMode'),
topologiesLoaded: state.get('topologiesLoaded'),
currentTopology: state.get('currentTopology'),
showingTimeTravel: state.get('showingTimeTravel'),
timeTravelTransitioning: state.get('timeTravelTransitioning'),
pausedAt: state.get('pausedAt'),
};
}
Expand All @@ -137,6 +109,5 @@ export default connect(
{
resumeTime,
pauseTimeAtNow,
startTimeTravel,
}
)(TimeControl);
53 changes: 26 additions & 27 deletions client/app/scripts/components/time-travel-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import React from 'react';
import moment from 'moment';
import styled from 'styled-components';
import { connect } from 'react-redux';
import { TimeTravel } from 'weaveworks-ui-components';

import { trackAnalyticsEvent } from '../utils/tracking-utils';
import { jumpToTime } from '../actions/app-actions';
import { jumpToTime, resumeTime, pauseTimeAtNow } from '../actions/app-actions';


const TimeTravelContainer = styled.div`
transition: all .15s ease-in-out;
position: relative;
overflow: hidden;
height: 0;
${props => props.visible && `
height: 105px;
`}
`;

class TimeTravelWrapper extends React.Component {
constructor(props, context) {
super(props, context);

this.handleLiveModeChange = this.handleLiveModeChange.bind(this);

this.trackTimestampEdit = this.trackTimestampEdit.bind(this);
this.trackTimelinePanButtonClick = this.trackTimelinePanButtonClick.bind(this);
this.trackTimelineLabelClick = this.trackTimelineLabelClick.bind(this);
Expand Down Expand Up @@ -71,20 +61,29 @@ class TimeTravelWrapper extends React.Component {
});
}

handleLiveModeChange(showingLive) {
if (showingLive) {
this.props.resumeTime();
} else {
this.props.pauseTimeAtNow();
}
}

render() {
return (
<TimeTravelContainer visible={this.props.visible}>
<TimeTravel
timestamp={this.props.timestamp}
earliestTimestamp={this.props.earliestTimestamp}
onChangeTimestamp={this.props.jumpToTime}
onTimestampInputEdit={this.trackTimestampEdit}
onTimelinePanButtonClick={this.trackTimelinePanButtonClick}
onTimelineLabelClick={this.trackTimelineLabelClick}
onTimelineZoom={this.trackTimelineZoom}
onTimelinePan={this.trackTimelinePan}
/>
</TimeTravelContainer>
<TimeTravel
hasLiveMode
showingLive={this.props.showingLive}
onChangeLiveMode={this.handleLiveModeChange}
timestamp={this.props.timestamp}
earliestTimestamp={this.props.earliestTimestamp}
onChangeTimestamp={this.props.jumpToTime}
onTimestampInputEdit={this.trackTimestampEdit}
onTimelinePanButtonClick={this.trackTimelinePanButtonClick}
onTimelineLabelClick={this.trackTimelineLabelClick}
onTimelineZoom={this.trackTimelineZoom}
onTimelinePan={this.trackTimelinePan}
/>
);
}
}
Expand All @@ -102,7 +101,7 @@ function mapStateToProps(state, { params }) {
}

return {
visible: scopeState.get('showingTimeTravel'),
showingLive: !scopeState.get('pausedAt'),
topologyViewMode: scopeState.get('topologyViewMode'),
currentTopology: scopeState.get('currentTopology'),
earliestTimestamp: firstSeenConnectedAt,
Expand All @@ -112,5 +111,5 @@ function mapStateToProps(state, { params }) {

export default connect(
mapStateToProps,
{ jumpToTime },
{ jumpToTime, resumeTime, pauseTimeAtNow },
)(TimeTravelWrapper);
4 changes: 2 additions & 2 deletions client/app/scripts/constants/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ export const EDGE_WAYPOINTS_CAP = 10;

export const CANVAS_MARGINS = {
[GRAPH_VIEW_MODE]: {
top: 160, left: 80, right: 80, bottom: 150
top: 220, left: 80, right: 80, bottom: 150
},
[TABLE_VIEW_MODE]: {
top: 220, left: 40, right: 40, bottom: 30
},
[RESOURCE_VIEW_MODE]: {
top: 140, left: 210, right: 40, bottom: 150
top: 200, left: 210, right: 40, bottom: 150
},
};

Expand Down
4 changes: 0 additions & 4 deletions client/app/scripts/reducers/root.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export const initialState = makeMap({
selectedNetwork: null,
selectedNodeId: null,
showingHelp: false,
showingTimeTravel: false,
showingTroubleshootingMenu: false,
showingNetworks: false,
timeTravelTransitioning: false,
Expand Down Expand Up @@ -369,18 +368,15 @@ export function rootReducer(state = initialState, action) {

case ActionTypes.RESUME_TIME: {
state = state.set('timeTravelTransitioning', true);
state = state.set('showingTimeTravel', false);
return state.set('pausedAt', null);
}

case ActionTypes.PAUSE_TIME_AT_NOW: {
state = state.set('showingTimeTravel', false);
state = state.set('timeTravelTransitioning', false);
return state.set('pausedAt', moment().utc().format());
}

case ActionTypes.START_TIME_TRAVEL: {
state = state.set('showingTimeTravel', true);
state = state.set('timeTravelTransitioning', false);
return state.set('pausedAt', action.timestamp || moment().utc().format());
}
Expand Down
2 changes: 2 additions & 0 deletions client/app/scripts/selectors/time-travel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export const isPausedSelector = createSelector(
],
pausedAt => !!pausedAt
);

export const timeTravelSupportedSelector = state => state.getIn(['capabilities', 'historic_reports']);
Loading

0 comments on commit 2101a28

Please sign in to comment.