From f54038918972bae4b6226fd87d040cd90a1c2f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Mon, 29 Jan 2018 09:07:10 +0100 Subject: [PATCH] Move metrics poller to seperate class --- frontend/package.json | 2 + frontend/src/__tests__/.eslintrc | 5 ++ .../src/__tests__/metrics-poller-test.jsx | 63 +++++++++++++++++++ .../src/component/feature/list-component.jsx | 10 --- .../src/component/feature/list-container.jsx | 2 - .../component/feature/metric-component.jsx | 7 --- .../src/component/feature/view-component.jsx | 4 +- frontend/src/index.jsx | 3 + frontend/src/metrics-poller.js | 31 +++++++++ frontend/yarn.lock | 25 ++++++++ 10 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 frontend/src/__tests__/.eslintrc create mode 100644 frontend/src/__tests__/metrics-poller-test.jsx create mode 100644 frontend/src/metrics-poller.js diff --git a/frontend/package.json b/frontend/package.json index 3941ef39e4..6fbca36625 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -72,12 +72,14 @@ "eslint-config-finn-react": "^2.0.0", "eslint-plugin-react": "^7.3.0", "extract-text-webpack-plugin": "^3.0.0", + "fetch-mock": "^6.0.0", "identity-obj-proxy": "^3.0.0", "jest": "^22.1.2", "node-sass": "^4.5.3", "prettier": "^1.8.2", "react-test-renderer": "^15.6.1", "redux-devtools": "^3.3.1", + "redux-mock-store": "^1.5.1", "sass-loader": "^6.0.6", "style-loader": "^0.19.0", "toolbox-loader": "0.0.3", diff --git a/frontend/src/__tests__/.eslintrc b/frontend/src/__tests__/.eslintrc new file mode 100644 index 0000000000..eba2077219 --- /dev/null +++ b/frontend/src/__tests__/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "jest": true + } +} diff --git a/frontend/src/__tests__/metrics-poller-test.jsx b/frontend/src/__tests__/metrics-poller-test.jsx new file mode 100644 index 0000000000..6a3c7d6e1e --- /dev/null +++ b/frontend/src/__tests__/metrics-poller-test.jsx @@ -0,0 +1,63 @@ +import configureStore from 'redux-mock-store'; +import thunkMiddleware from 'redux-thunk'; +import fetchMock from 'fetch-mock'; +import MetricsPoller from '../metrics-poller'; + +const mockStore = configureStore([thunkMiddleware]); + +describe('metrics-poller.js', () => { + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + test('Should not start poller before toggles are recieved', () => { + const initialState = { features: [{ name: 'test1' }] }; + const store = mockStore(initialState); + fetchMock.getOnce('api/admin/metrics/feature-toggles', { + body: { lastHour: {}, lastMinute: {} }, + headers: { 'content-type': 'application/json' }, + }); + + const metricsPoller = new MetricsPoller(store); + metricsPoller.start(); + + expect(metricsPoller.timer).toBeUndefined(); + }); + + test('Should not start poller when state does not contain toggles', () => { + const initialState = { features: [] }; + const store = mockStore(initialState); + + const metricsPoller = new MetricsPoller(store); + metricsPoller.start(); + + store.dispatch({ + type: 'some', + receivedAt: Date.now(), + }); + + expect(metricsPoller.timer).toBeUndefined(); + }); + + test('Should start poller when state gets toggles', () => { + fetchMock.getOnce('api/admin/metrics/feature-toggles', { + body: { lastHour: {}, lastMinute: {} }, + headers: { 'content-type': 'application/json' }, + }); + + const initialState = { features: [{ name: 'test1' }] }; + const store = mockStore(initialState); + + const metricsPoller = new MetricsPoller(store); + metricsPoller.start(); + + store.dispatch({ + type: 'RECEIVE_FEATURE_TOGGLES', + featureToggles: [{ name: 'test' }], + receivedAt: Date.now(), + }); + + expect(metricsPoller.timer).toBeDefined(); + }); +}); diff --git a/frontend/src/component/feature/list-component.jsx b/frontend/src/component/feature/list-component.jsx index e2a589fb4b..683406278e 100644 --- a/frontend/src/component/feature/list-component.jsx +++ b/frontend/src/component/feature/list-component.jsx @@ -12,7 +12,6 @@ export default class FeatureListComponent extends React.PureComponent { features: PropTypes.array.isRequired, featureMetrics: PropTypes.object.isRequired, fetchFeatureToggles: PropTypes.func.isRequired, - fetchFeatureMetrics: PropTypes.func.isRequired, updateSetting: PropTypes.func.isRequired, settings: PropTypes.object, }; @@ -23,15 +22,6 @@ export default class FeatureListComponent extends React.PureComponent { componentDidMount() { this.props.fetchFeatureToggles(); - this.props.fetchFeatureMetrics(); - this.timer = setInterval(() => { - // this.props.fetchFeatureToggles(); - this.props.fetchFeatureMetrics(); - }, 5000); - } - - componentWillUnmount() { - clearInterval(this.timer); } toggleMetrics() { diff --git a/frontend/src/component/feature/list-container.jsx b/frontend/src/component/feature/list-container.jsx index c20c2d1b1b..412b50376c 100644 --- a/frontend/src/component/feature/list-container.jsx +++ b/frontend/src/component/feature/list-container.jsx @@ -1,6 +1,5 @@ import { connect } from 'react-redux'; import { toggleFeature, fetchFeatureToggles } from '../../store/feature-actions'; -import { fetchFeatureMetrics } from '../../store/feature-metrics-actions'; import { updateSettingForGroup } from '../../store/settings/actions'; import FeatureListComponent from './list-component'; @@ -74,7 +73,6 @@ const mapStateToProps = state => { const mapDispatchToProps = { toggleFeature, fetchFeatureToggles, - fetchFeatureMetrics, updateSetting: updateSettingForGroup('feature'), }; diff --git a/frontend/src/component/feature/metric-component.jsx b/frontend/src/component/feature/metric-component.jsx index 407c93c10a..b0c61a777c 100644 --- a/frontend/src/component/feature/metric-component.jsx +++ b/frontend/src/component/feature/metric-component.jsx @@ -37,13 +37,6 @@ export default class MetricComponent extends React.Component { componentWillMount() { this.props.fetchSeenApps(); this.props.fetchFeatureMetrics(); - this.timer = setInterval(() => { - this.props.fetchFeatureMetrics(); - }, 5000); - } - - componentWillUnmount() { - clearInterval(this.timer); } render() { diff --git a/frontend/src/component/feature/view-component.jsx b/frontend/src/component/feature/view-component.jsx index db5998e97e..6f03ad49a9 100644 --- a/frontend/src/component/feature/view-component.jsx +++ b/frontend/src/component/feature/view-component.jsx @@ -25,8 +25,8 @@ export default class ViewFeatureToggleComponent extends React.Component { features: PropTypes.array.isRequired, toggleFeature: PropTypes.func.isRequired, removeFeatureToggle: PropTypes.func.isRequired, - fetchFeatureToggles: PropTypes.array.isRequired, - featureToggle: PropTypes.object.isRequired, + fetchFeatureToggles: PropTypes.func.isRequired, + featureToggle: PropTypes.object, }; componentWillMount() { diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 981df58e71..c8d92239a9 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -11,6 +11,7 @@ import thunkMiddleware from 'redux-thunk'; import { createStore, applyMiddleware, compose } from 'redux'; import store from './store'; +import MetricsPoller from './metrics-poller'; import App from './component/app'; import Features from './page/features'; @@ -34,6 +35,8 @@ if (process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_C } const unleashStore = createStore(store, composeEnhancers(applyMiddleware(thunkMiddleware))); +const metricsPoller = new MetricsPoller(unleashStore); +metricsPoller.start(); // "pageTitle" and "link" attributes are for internal usage only diff --git a/frontend/src/metrics-poller.js b/frontend/src/metrics-poller.js new file mode 100644 index 0000000000..3c1c331e2f --- /dev/null +++ b/frontend/src/metrics-poller.js @@ -0,0 +1,31 @@ +import { fetchFeatureMetrics } from './store/feature-metrics-actions'; + +class MetricsPoller { + constructor(store) { + this.store = store; + this.timer = undefined; + } + + start() { + this.store.subscribe(() => { + const features = this.store.getState().features; + if (!this.timer && features.length > 0) { + this.timer = setInterval(this.fetchMetrics.bind(this), 5000); + this.fetchMetrics(); + } + }); + } + + fetchMetrics() { + this.store.dispatch(fetchFeatureMetrics()); + } + + destroy() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = undefined; + } + } +} + +export default MetricsPoller; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e004e6dedd..05d1129552 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2462,6 +2462,13 @@ fbjs@^0.8.16, fbjs@^0.8.9: setimmediate "^1.0.5" ua-parser-js "^0.7.9" +fetch-mock@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-6.0.0.tgz#4edb5acefa8ea90d7eb4213130ab73137fac9df1" + dependencies: + glob-to-regexp "^0.3.0" + path-to-regexp "^2.1.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2684,6 +2691,10 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -3858,6 +3869,10 @@ lodash.isequal@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4493,6 +4508,10 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-to-regexp@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.1.0.tgz#7e30f9f5b134bd6a28ffc2e3ef1e47075ac5259b" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -5204,6 +5223,12 @@ redux-devtools@^3.3.1: prop-types "^15.5.7" redux-devtools-instrument "^1.0.1" +redux-mock-store@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.1.tgz#fca4335392e66605420b5559fe02fc5b8bb6d63c" + dependencies: + lodash.isplainobject "^4.0.6" + redux-thunk@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"