From 0f86ad1bed392506c440ce0f719c3dc9bf07f90c Mon Sep 17 00:00:00 2001
From: jpellizzari <jordan@weave.works>
Date: Mon, 20 Mar 2017 12:30:32 -0700
Subject: [PATCH] Prevent client polling after shutdown

---
 client/app/scripts/actions/app-actions.js    | 19 +++---
 client/app/scripts/components/app.js         |  9 +--
 client/app/scripts/constants/action-types.js |  1 +
 client/app/scripts/reducers/root.js          |  5 ++
 client/app/scripts/utils/web-api-utils.js    | 69 ++++++++++++--------
 5 files changed, 62 insertions(+), 41 deletions(-)

diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js
index 5bb0a74dc8..2199797349 100644
--- a/client/app/scripts/actions/app-actions.js
+++ b/client/app/scripts/actions/app-actions.js
@@ -17,7 +17,7 @@ import {
   getNodeDetails,
   getTopologies,
   deletePipe,
-  stopTopologyPolling,
+  stopPolling,
   teardownWebsockets,
 } from '../utils/web-api-utils';
 import { getCurrentTopologyUrl } from '../utils/topology-utils';
@@ -720,20 +720,17 @@ export function toggleTroubleshootingMenu(ev) {
 
 export function changeInstance() {
   return (dispatch, getState) => {
-    dispatch({ type: ActionTypes.CHANGE_INSTANCE });
+    dispatch({
+      type: ActionTypes.CHANGE_INSTANCE
+    });
     updateRoute(getState);
-    const state = getState();
-    getTopologies(activeTopologyOptionsSelector(state), dispatch);
-    getNodesDelta(
-      getCurrentTopologyUrl(state),
-      activeTopologyOptionsSelector(state),
-      dispatch,
-      true // forces websocket teardown and reconnect to new instance
-    );
   };
 }
 
 export function shutdown() {
-  stopTopologyPolling();
+  stopPolling();
   teardownWebsockets();
+  return {
+    type: ActionTypes.SHUTDOWN
+  };
 }
diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index adc1251f48..19d7d92454 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -42,9 +42,10 @@ class App extends React.Component {
     window.addEventListener('keyup', this.onKeyUp);
 
     getRouter(this.props.dispatch, this.props.urlState).start({hashbang: true});
-    if (!this.props.routeSet) {
-      // dont request topologies when already done via router
-      getTopologies(this.props.activeTopologyOptions, this.props.dispatch);
+    if (!this.props.routeSet || process.env.WEAVE_CLOUD) {
+      // dont request topologies when already done via router.
+      // If running as a component, always request topologies when the app mounts.
+      getTopologies(this.props.activeTopologyOptions, this.props.dispatch, true);
     }
     getApiDetails(this.props.dispatch);
   }
@@ -52,7 +53,7 @@ class App extends React.Component {
   componentWillUnmount() {
     window.removeEventListener('keypress', this.onKeyPress);
     window.removeEventListener('keyup', this.onKeyUp);
-    shutdown();
+    this.props.dispatch(shutdown());
   }
 
   onKeyUp(ev) {
diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js
index d18818c6c7..fbf0a45f60 100644
--- a/client/app/scripts/constants/action-types.js
+++ b/client/app/scripts/constants/action-types.js
@@ -62,6 +62,7 @@ const ACTION_TYPES = [
   'SET_GRID_MODE',
   'CHANGE_INSTANCE',
   'TOGGLE_CONTRAST_MODE',
+  'SHUTDOWN'
 ];
 
 export default zipObject(ACTION_TYPES, ACTION_TYPES);
diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js
index 49fe78d123..b2bf96441a 100644
--- a/client/app/scripts/reducers/root.js
+++ b/client/app/scripts/reducers/root.js
@@ -744,6 +744,11 @@ export function rootReducer(state = initialState, action) {
       return state.set('contrastMode', action.enabled);
     }
 
+    case ActionTypes.SHUTDOWN: {
+      state = clearNodes(state);
+      return state.set('nodesLoaded', false);
+    }
+
     default: {
       return state;
     }
diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js
index 90423118c6..6fa2331c02 100644
--- a/client/app/scripts/utils/web-api-utils.js
+++ b/client/app/scripts/utils/web-api-utils.js
@@ -40,6 +40,7 @@ let apiDetailsTimer = 0;
 let controlErrorTimer = 0;
 let createWebsocketAt = 0;
 let firstMessageOnWebsocketAt = 0;
+let continuePolling = true;
 
 export function buildOptionsQuery(options) {
   if (options) {
@@ -111,9 +112,11 @@ function createWebsocket(topologyUrl, optionsQuery, dispatch) {
     socket = null;
     dispatch(closeWebsocket());
 
-    reconnectTimer = setTimeout(() => {
-      createWebsocket(topologyUrl, optionsQuery, dispatch);
-    }, reconnectTimerInterval);
+    if (continuePolling) {
+      reconnectTimer = setTimeout(() => {
+        createWebsocket(topologyUrl, optionsQuery, dispatch);
+      }, reconnectTimerInterval);
+    }
   };
 
   socket.onerror = () => {
@@ -172,35 +175,44 @@ export function getAllNodes(getState, dispatch) {
     Promise.resolve());
 }
 
-export function getTopologies(options, dispatch) {
+export function getTopologies(options, dispatch, initialPoll) {
+  // Used to resume polling when navigating between pages in Weave Cloud.
+  continuePolling = initialPoll === true ? true : continuePolling;
   clearTimeout(topologyTimer);
   const optionsQuery = buildOptionsQuery(options);
   const url = `${getApiPath()}/api/topology?${optionsQuery}`;
   doRequest({
     url,
     success: (res) => {
-      dispatch(receiveTopologies(res));
-      topologyTimer = setTimeout(() => {
-        getTopologies(options, dispatch);
-      }, TOPOLOGY_INTERVAL);
+      if (continuePolling) {
+        dispatch(receiveTopologies(res));
+        topologyTimer = setTimeout(() => {
+          getTopologies(options, dispatch);
+        }, TOPOLOGY_INTERVAL);
+      }
     },
-    error: (err) => {
-      log(`Error in topology request: ${err.responseText}`);
+    error: (req) => {
+      log(`Error in topology request: ${req.responseText}`);
       dispatch(receiveError(url));
-      topologyTimer = setTimeout(() => {
-        getTopologies(options, dispatch);
-      }, TOPOLOGY_INTERVAL);
+      // Only retry in stand-alone mode
+      if (continuePolling) {
+        topologyTimer = setTimeout(() => {
+          getTopologies(options, dispatch);
+        }, TOPOLOGY_INTERVAL);
+      }
     }
   });
 }
 
-export function getNodesDelta(topologyUrl, options, dispatch, forceReload) {
+export function getNodesDelta(topologyUrl, options, dispatch) {
   const optionsQuery = buildOptionsQuery(options);
   // Only recreate websocket if url changed or if forced (weave cloud instance reload);
   // Check for truthy options and that options have changed.
   const isNewOptions = currentOptions && currentOptions !== optionsQuery;
-  const isNewUrl = topologyUrl && (topologyUrl !== currentUrl || isNewOptions);
-  if (forceReload || isNewUrl) {
+  const isNewUrl = topologyUrl !== currentUrl || isNewOptions;
+  // `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) || (topologyUrl && isNewUrl)) {
     createWebsocket(topologyUrl, optionsQuery, dispatch);
     currentUrl = topologyUrl;
     currentOptions = optionsQuery;
@@ -250,16 +262,20 @@ export function getApiDetails(dispatch) {
     url,
     success: (res) => {
       dispatch(receiveApiDetails(res));
-      apiDetailsTimer = setTimeout(() => {
-        getApiDetails(dispatch);
-      }, API_INTERVAL);
+      if (continuePolling) {
+        apiDetailsTimer = setTimeout(() => {
+          getApiDetails(dispatch);
+        }, API_INTERVAL);
+      }
     },
-    error: (err) => {
-      log(`Error in api details request: ${err.responseText}`);
+    error: (req) => {
+      log(`Error in api details request: ${req.responseText}`);
       receiveError(url);
-      apiDetailsTimer = setTimeout(() => {
-        getApiDetails(dispatch);
-      }, API_INTERVAL / 2);
+      if (continuePolling) {
+        apiDetailsTimer = setTimeout(() => {
+          getApiDetails(dispatch);
+        }, API_INTERVAL / 2);
+      }
     }
   });
 }
@@ -353,9 +369,10 @@ export function getPipeStatus(pipeId, dispatch) {
   });
 }
 
-export function stopTopologyPolling() {
+export function stopPolling() {
+  clearTimeout(apiDetailsTimer);
   clearTimeout(topologyTimer);
-  topologyTimer = 0;
+  continuePolling = false;
 }
 
 export function teardownWebsockets() {