From f49770c06216900db7b14b6784e6502c1fed65e6 Mon Sep 17 00:00:00 2001 From: Filip Barl Date: Tue, 26 Jun 2018 16:39:50 +0200 Subject: [PATCH 1/2] Support TimeTravel injection. --- client/app/scripts/actions/app-actions.js | 41 ++--- client/app/scripts/components/app.js | 17 +- .../scripts/components/time-travel-wrapper.js | 153 ++---------------- 3 files changed, 37 insertions(+), 174 deletions(-) diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 514190450..881544da4 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -339,15 +339,14 @@ 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(getScopeState); - if (!getScopeState().get('nodesLoaded')) { - getNodes(getScopeState, dispatch); - if (isResourceViewModeSelector(getScopeState())) { - getResourceViewNodesSnapshot(getScopeState(), dispatch); + updateRoute(getState); + if (!getState().get('nodesLoaded')) { + getNodes(getState, dispatch); + if (isResourceViewModeSelector(getState())) { + getResourceViewNodesSnapshot(getState(), dispatch); } } }; @@ -578,17 +577,16 @@ export function receiveNodesDelta(delta) { export function resumeTime() { return (dispatch, getState) => { - const getScopeState = () => getState().scope || getState(); - if (isPausedSelector(getScopeState())) { + if (isPausedSelector(getState())) { dispatch({ type: ActionTypes.RESUME_TIME }); - updateRoute(getScopeState); + updateRoute(getState); // After unpausing, all of the following calls will re-activate polling. - getTopologies(getScopeState, dispatch); - getNodes(getScopeState, dispatch, true); - if (isResourceViewModeSelector(getScopeState())) { - getResourceViewNodesSnapshot(getScopeState(), dispatch); + getTopologies(getState, dispatch); + getNodes(getState, dispatch, true); + if (isResourceViewModeSelector(getState())) { + getResourceViewNodesSnapshot(getState(), dispatch); } } }; @@ -622,16 +620,15 @@ export function receiveNodes(nodes) { export function jumpToTime(timestamp) { return (dispatch, getState) => { - const getScopeState = () => getState().scope || getState(); dispatch({ type: ActionTypes.JUMP_TO_TIME, timestamp, }); - updateRoute(getScopeState); - getNodes(getScopeState, dispatch); - getTopologies(getScopeState, dispatch); - if (isResourceViewModeSelector(getScopeState())) { - getResourceViewNodesSnapshot(getScopeState(), dispatch); + updateRoute(getState); + getNodes(getState, dispatch); + getTopologies(getState, dispatch); + if (isResourceViewModeSelector(getState())) { + getResourceViewNodesSnapshot(getState(), dispatch); } }; } @@ -870,12 +867,6 @@ export function getImagesForService(orgId, serviceId) { }; } -export function getFluxHistory(...params) { - return (dispatch, getState, { actions }) => ( - dispatch(actions.getFluxHistory(...params)) - ); -} - export function setMonitorState(monitor) { return { type: ActionTypes.MONITOR_STATE, diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 60a645a6b..e3c0ccf64 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -1,5 +1,6 @@ import debug from 'debug'; import React from 'react'; +import PropTypes from 'prop-types'; import classNames from 'classnames'; import { connect } from 'react-redux'; import { debounce } from 'lodash'; @@ -11,7 +12,6 @@ import Logo from './logo'; import Footer from './footer'; import Sidebar from './sidebar'; import HelpPanel from './help-panel'; -import CloudFeature from './cloud-feature'; import TroubleshootingMenu from './troubleshooting-menu'; import Search from './search'; import Status from './status'; @@ -59,7 +59,6 @@ import { const keyPressLog = debug('scope:app-key-press'); - class App extends React.Component { constructor(props, context) { super(props, context); @@ -205,11 +204,7 @@ class App extends React.Component { {showingDetails &&
}
- {timeTravelSupported && ( - - - - )} + {timeTravelSupported && this.props.renderTimeTravel()}
@@ -266,8 +261,14 @@ function mapStateToProps(state) { }; } +App.propTypes = { + renderTimeTravel: PropTypes.func, + monitor: PropTypes.bool, +}; + App.defaultProps = { - monitor: false + renderTimeTravel: () => , + monitor: false, }; export default connect(mapStateToProps)(App); diff --git a/client/app/scripts/components/time-travel-wrapper.js b/client/app/scripts/components/time-travel-wrapper.js index 44737d837..bacc058e5 100644 --- a/client/app/scripts/components/time-travel-wrapper.js +++ b/client/app/scripts/components/time-travel-wrapper.js @@ -1,72 +1,10 @@ import React from 'react'; -import moment from 'moment'; import { connect } from 'react-redux'; import { TimeTravel } from 'weaveworks-ui-components'; -import { get, orderBy, debounce } from 'lodash'; -import { trackAnalyticsEvent } from '../utils/tracking-utils'; -import { jumpToTime, resumeTime, pauseTimeAtNow, getFluxHistory } from '../actions/app-actions'; - -// Load deployments in timeline only on zoom levels up to this range. -const MAX_DEPLOYMENTS_RANGE_SECS = moment.duration(2, 'weeks').asSeconds(); - -// Reused from Service UI. -const FLUX_ALL_SERVICES = ''; +import { jumpToTime, resumeTime, pauseTimeAtNow } from '../actions/app-actions'; class TimeTravelWrapper extends React.Component { - constructor(props) { - super(props); - - this.state = { - isLoadingDeployments: false, - visibleRangeStartAtSec: null, - visibleRangeEndAtSec: null, - }; - - this.debouncedUpdateVisibleRange = debounce(this.updateVisibleRange, 500); - } - - trackTimestampEdit = () => { - trackAnalyticsEvent('scope.time.timestamp.edit', { - layout: this.props.topologyViewMode, - topologyId: this.props.currentTopology.get('id'), - parentTopologyId: this.props.currentTopology.get('parentId'), - }); - } - - trackTimelinePanButtonClick = () => { - trackAnalyticsEvent('scope.time.timeline.pan.button.click', { - layout: this.props.topologyViewMode, - topologyId: this.props.currentTopology.get('id'), - parentTopologyId: this.props.currentTopology.get('parentId'), - }); - } - - trackTimelineLabelClick = () => { - trackAnalyticsEvent('scope.time.timeline.label.click', { - layout: this.props.topologyViewMode, - topologyId: this.props.currentTopology.get('id'), - parentTopologyId: this.props.currentTopology.get('parentId'), - }); - } - - trackTimelinePan = () => { - trackAnalyticsEvent('scope.time.timeline.pan', { - layout: this.props.topologyViewMode, - topologyId: this.props.currentTopology.get('id'), - parentTopologyId: this.props.currentTopology.get('parentId'), - }); - } - - trackTimelineZoom = (zoomedPeriod) => { - trackAnalyticsEvent('scope.time.timeline.zoom', { - layout: this.props.topologyViewMode, - topologyId: this.props.currentTopology.get('id'), - parentTopologyId: this.props.currentTopology.get('parentId'), - zoomedPeriod, - }); - } - handleLiveModeChange = (showingLive) => { if (showingLive) { this.props.resumeTime(); @@ -75,96 +13,29 @@ class TimeTravelWrapper extends React.Component { } } - - updateVisibleRange = ({ startAt, endAt }) => { - const { orgId } = this.props.params; - - const visibleRangeEndAtSec = moment(endAt).unix(); - const visibleRangeStartAtSec = moment(startAt).unix(); - this.setState({ visibleRangeStartAtSec, visibleRangeEndAtSec }); - - // Load deployment annotations only if not zoomed out too much. - // See https://github.com/weaveworks/service-ui/issues/1858. - const visibleRangeSec = visibleRangeEndAtSec - visibleRangeStartAtSec; - if (visibleRangeSec < MAX_DEPLOYMENTS_RANGE_SECS) { - this.setState({ isLoadingDeployments: true }); - this.props - .getFluxHistory(orgId, FLUX_ALL_SERVICES, null, endAt, true, startAt) - .then(() => { - this.setState({ isLoadingDeployments: false }); - }); - } - } - render() { - const { - visibleRangeStartAtSec, - visibleRangeEndAtSec, - } = this.state; - - // Don't pass any deployments that are outside of the timeline visible range. - const visibleDeployments = this.props.deployments.filter((deployment) => { - const deploymentAtSec = moment(deployment.Stamp).unix(); - return ( - visibleRangeStartAtSec <= deploymentAtSec && - deploymentAtSec <= visibleRangeEndAtSec - ); - }); - return ( -
- -
+ ); } } -function mapStateToProps(state, { params }) { - const orgId = params && params.orgId; - let firstSeenConnectedAt; - - // If we're in the Weave Cloud context, use firstSeeConnectedAt as the earliest timestamp. - if (state.root && state.root.instances) { - const serviceInstance = state.root.instances[orgId]; - if (serviceInstance && serviceInstance.firstSeenConnectedAt) { - firstSeenConnectedAt = moment(serviceInstance.firstSeenConnectedAt).utc().format(); - } - } - - const unsortedDeployments = get( - state, - ['root', 'fluxInstanceHistory', orgId, FLUX_ALL_SERVICES], - [] - ); - +function mapStateToProps(state) { return { - showingLive: !state.scope.get('pausedAt'), - topologyViewMode: state.scope.get('topologyViewMode'), - currentTopology: state.scope.get('currentTopology'), - deployments: orderBy(unsortedDeployments, ['Stamp'], ['desc']), - earliestTimestamp: firstSeenConnectedAt, - timestamp: state.scope.get('pausedAt'), + showingLive: !state.get('pausedAt'), + timestamp: state.get('pausedAt'), }; } export default connect( mapStateToProps, { - jumpToTime, resumeTime, pauseTimeAtNow, getFluxHistory + jumpToTime, resumeTime, pauseTimeAtNow }, )(TimeTravelWrapper); From a7282552ab143c7226ab43c972a783ac4b870be4 Mon Sep 17 00:00:00 2001 From: Filip Barl Date: Wed, 27 Jun 2018 11:27:56 +0200 Subject: [PATCH 2/2] Simplified TimeTravel state update from the route. --- client/app/scripts/actions/app-actions.js | 46 +++++++------------- client/app/scripts/constants/action-types.js | 1 - client/app/scripts/reducers/root.js | 5 --- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 881544da4..755375af7 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -549,8 +549,7 @@ export function receiveNodeDetails(details, requestTimestamp) { export function receiveNodesDelta(delta) { return (dispatch, getState) => { - const getScopeState = () => getState().scope || getState(); - if (!isPausedSelector(getScopeState())) { + if (!isPausedSelector(getState())) { // 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. @@ -560,7 +559,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 (getScopeState().get('timeTravelTransitioning')) { + if (getState().get('timeTravelTransitioning')) { dispatch({ type: ActionTypes.FINISH_TIME_TRAVEL_TRANSITION }); } @@ -592,25 +591,6 @@ export function resumeTime() { }; } -export function startTimeTravel(timestamp = null) { - return (dispatch, getState) => { - dispatch({ - type: ActionTypes.START_TIME_TRAVEL, - timestamp, - }); - updateRoute(getState); - if (!getState().get('nodesLoaded')) { - getNodes(getState, dispatch); - if (isResourceViewModeSelector(getState())) { - getResourceViewNodesSnapshot(getState(), dispatch); - } - } else { - // Get most recent details before freezing the state. - getNodeDetails(getState, dispatch); - } - }; -} - export function receiveNodes(nodes) { return { type: ActionTypes.RECEIVE_NODES, @@ -625,10 +605,15 @@ export function jumpToTime(timestamp) { timestamp, }); updateRoute(getState); - getNodes(getState, dispatch); getTopologies(getState, dispatch); - if (isResourceViewModeSelector(getState())) { - getResourceViewNodesSnapshot(getState(), dispatch); + if (!getState().get('nodesLoaded')) { + getNodes(getState, dispatch); + if (isResourceViewModeSelector(getState())) { + getResourceViewNodesSnapshot(getState(), dispatch); + } + } else { + // Get most recent details before freezing the state. + getNodeDetails(getState, dispatch); } }; } @@ -643,15 +628,14 @@ export function receiveNodesForTopology(nodes, topologyId) { export function receiveTopologies(topologies) { return (dispatch, getState) => { - const getScopeState = () => getState().scope || getState(); - const firstLoad = !getScopeState().get('topologiesLoaded'); + const firstLoad = !getState().get('topologiesLoaded'); dispatch({ type: ActionTypes.RECEIVE_TOPOLOGIES, topologies }); - getNodes(getScopeState, dispatch); + getNodes(getState, dispatch); // Populate search matches on first load - const state = getScopeState(); + const state = getState(); if (firstLoad && state.get('searchQuery')) { dispatch(focusSearch()); } @@ -684,7 +668,7 @@ export function receiveApiDetails(apiDetails) { // we have no prior info on whether time travel would be available. if (isFirstTime && pausedAt) { if (apiDetails.capabilities && apiDetails.capabilities.historic_reports) { - dispatch(startTimeTravel(pausedAt)); + dispatch(jumpToTime(pausedAt)); } else { dispatch(pauseTimeAtNow()); } @@ -793,7 +777,7 @@ export function route(urlState) { if (!urlState.pausedAt) { dispatch(resumeTime()); } else { - dispatch(startTimeTravel(urlState.pausedAt)); + dispatch(jumpToTime(urlState.pausedAt)); } // update all request workers with new options getTopologies(getState, dispatch); diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index d13a1d072..f2931007d 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -63,7 +63,6 @@ const ACTION_TYPES = [ 'SHOW_NETWORKS', 'SHUTDOWN', 'SORT_ORDER_CHANGED', - 'START_TIME_TRAVEL', 'TOGGLE_CONTRAST_MODE', 'TOGGLE_TROUBLESHOOTING_MENU', 'UNHOVER_METRIC', diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index 5ddaacfcd..823335dd2 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -376,11 +376,6 @@ export function rootReducer(state = initialState, action) { return state.set('pausedAt', moment().utc().format()); } - case ActionTypes.START_TIME_TRAVEL: { - state = state.set('timeTravelTransitioning', false); - return state.set('pausedAt', action.timestamp || moment().utc().format()); - } - case ActionTypes.JUMP_TO_TIME: { state = state.set('timeTravelTransitioning', true); return state.set('pausedAt', action.timestamp);