diff --git a/packages/unleash-frontend-next/.eslintrc b/packages/unleash-frontend-next/.eslintrc index 8f68bb4a87..ee0b68514f 100644 --- a/packages/unleash-frontend-next/.eslintrc +++ b/packages/unleash-frontend-next/.eslintrc @@ -2,5 +2,8 @@ "extends": [ "finn", "finn/node" - ] + ], + "rules": { + "no-shadow": 0 + } } diff --git a/packages/unleash-frontend-next/src/component/metrics/metrics-component.js b/packages/unleash-frontend-next/src/component/metrics/metrics-component.js new file mode 100644 index 0000000000..4b47bf2b42 --- /dev/null +++ b/packages/unleash-frontend-next/src/component/metrics/metrics-component.js @@ -0,0 +1,31 @@ +import React, { Component } from 'react'; +import { List, ListItem, ListSubHeader, ListDivider } from 'react-toolbox/lib/list'; +import Chip from 'react-toolbox/lib/chip'; + +class Metrics extends Component { + + componentDidMount () { + this.props.fetchMetrics(); + } + + render () { + const { globalCount, apps, clientList } = this.props; + + return ( + + Total of {globalCount} toggles checked from {apps.length} apps ({apps.join(', ')})} /> + + {clientList.map(({ name, count, ping, appName }, i) => + {count}]} + key={name + i} + caption={appName} + legend={`${name} pinged ${ping}`} /> + )} + + ); + } +} + + +export default Metrics; diff --git a/packages/unleash-frontend-next/src/component/metrics/metrics-container.js b/packages/unleash-frontend-next/src/component/metrics/metrics-container.js new file mode 100644 index 0000000000..e4ab36af79 --- /dev/null +++ b/packages/unleash-frontend-next/src/component/metrics/metrics-container.js @@ -0,0 +1,39 @@ +import { connect } from 'react-redux'; +import Metrics from './metrics-component'; +import { fetchMetrics } from '../../store/metrics-actions'; + +const mapStateToProps = (state) => { + const globalCount = state.metrics.get('globalCount'); + const apps = state.metrics.get('apps').toArray(); + const clients = state.metrics.get('clients').toJS(); + + const clientList = Object + .keys(clients) + .map((k) => { + const client = clients[k]; + return { + name: k, + appName: client.appName, + count: client.count, + ping: new Date(client.ping), + }; + }) + .sort((a, b) => (a.ping > b.ping ? -1 : 1)); + + + /* + Possible stuff to ask/answer: + * toggles in use but not in unleash-server + * nr of toggles using fallbackValue + * strategies implemented but not used + */ + return { + globalCount, + apps, + clientList, + }; +}; + +const MetricsContainer = connect(mapStateToProps, { fetchMetrics })(Metrics); + +export default MetricsContainer; diff --git a/packages/unleash-frontend-next/src/component/nav.jsx b/packages/unleash-frontend-next/src/component/nav.jsx index bded15c70b..41a7e1e5b3 100644 --- a/packages/unleash-frontend-next/src/component/nav.jsx +++ b/packages/unleash-frontend-next/src/component/nav.jsx @@ -14,10 +14,11 @@ export default class UnleashNav extends Component { return ( - {createListItem('/features', 'Feature Toggles')} + {createListItem('/features', 'Feature toggles')} {createListItem('/strategies', 'Strategies')} - {createListItem('/history', 'Event History')} - {createListItem('/archive', 'Archived Toggles')} + {createListItem('/history', 'Event history')} + {createListItem('/archive', 'Archived toggles')} + {createListItem('/metrics', 'Client metrics')} diff --git a/packages/unleash-frontend-next/src/data/metrics-api.js b/packages/unleash-frontend-next/src/data/metrics-api.js new file mode 100644 index 0000000000..cb06ad9443 --- /dev/null +++ b/packages/unleash-frontend-next/src/data/metrics-api.js @@ -0,0 +1,20 @@ +const URI = '/metrics'; + +function throwIfNotSuccess (response) { + if (!response.ok) { + let error = new Error('API call failed'); + error.status = response.status; + throw error; + } + return response; +} + +function fetchAll () { + return fetch(URI) + .then(throwIfNotSuccess) + .then(response => response.json()); +} + +module.exports = { + fetchAll, +}; diff --git a/packages/unleash-frontend-next/src/index.jsx b/packages/unleash-frontend-next/src/index.jsx index a45ca4a3ce..8133a8a81d 100644 --- a/packages/unleash-frontend-next/src/index.jsx +++ b/packages/unleash-frontend-next/src/index.jsx @@ -15,6 +15,7 @@ import Strategies from './page/strategies'; import CreateStrategies from './page/strategies/create'; import HistoryPage from './page/history'; import Archive from './page/archive'; +import Metrics from './page/metrics'; const unleashStore = createStore( store, @@ -35,6 +36,7 @@ ReactDOM.render( + , document.getElementById('app')); diff --git a/packages/unleash-frontend-next/src/page/metrics/index.js b/packages/unleash-frontend-next/src/page/metrics/index.js new file mode 100644 index 0000000000..c18da93b5c --- /dev/null +++ b/packages/unleash-frontend-next/src/page/metrics/index.js @@ -0,0 +1,6 @@ +import React from 'react'; +import Metrics from '../../component/metrics/metrics-container'; + +const render = () => ; + +export default render; diff --git a/packages/unleash-frontend-next/src/store/index.js b/packages/unleash-frontend-next/src/store/index.js index c064657e98..ef80a0a242 100644 --- a/packages/unleash-frontend-next/src/store/index.js +++ b/packages/unleash-frontend-next/src/store/index.js @@ -5,6 +5,7 @@ import input from './input-store'; import history from './history-store'; // eslint-disable-line import archive from './archive-store'; import error from './error-store'; +import metrics from './metrics-store'; const unleashStore = combineReducers({ features, @@ -13,6 +14,7 @@ const unleashStore = combineReducers({ history, archive, error, + metrics, }); export default unleashStore; diff --git a/packages/unleash-frontend-next/src/store/metrics-actions.js b/packages/unleash-frontend-next/src/store/metrics-actions.js new file mode 100644 index 0000000000..7010d3857d --- /dev/null +++ b/packages/unleash-frontend-next/src/store/metrics-actions.js @@ -0,0 +1,20 @@ +import api from '../data/metrics-api'; + +export const RECEIVE_METRICS = 'RECEIVE_METRICS'; +export const ERROR_RECEIVE_METRICS = 'ERROR_RECEIVE_METRICS'; + +const receiveMetrics = (json) => ({ + type: RECEIVE_METRICS, + value: json, +}); + +const errorReceiveMetrics = (statusCode) => ({ + type: ERROR_RECEIVE_METRICS, + statusCode, +}); + +export function fetchMetrics () { + return dispatch => api.fetchAll() + .then(json => dispatch(receiveMetrics(json))) + .catch(error => dispatch(errorReceiveMetrics(error))); +} diff --git a/packages/unleash-frontend-next/src/store/metrics-store.js b/packages/unleash-frontend-next/src/store/metrics-store.js new file mode 100644 index 0000000000..f0b7a4a650 --- /dev/null +++ b/packages/unleash-frontend-next/src/store/metrics-store.js @@ -0,0 +1,21 @@ +import { fromJS } from 'immutable'; +import { RECEIVE_METRICS } from './metrics-actions'; + +function getInitState () { + return fromJS({ + totalCount: 0, + apps: [], + clients: {}, + }); +} + +const historyStore = (state = getInitState(), action) => { + switch (action.type) { + case RECEIVE_METRICS: + return fromJS(action.value); + default: + return state; + } +}; + +export default historyStore; diff --git a/packages/unleash-frontend-next/webpack.config.js b/packages/unleash-frontend-next/webpack.config.js index 208b4a1e3d..edfd5096b1 100644 --- a/packages/unleash-frontend-next/webpack.config.js +++ b/packages/unleash-frontend-next/webpack.config.js @@ -73,6 +73,10 @@ module.exports = { target: 'http://localhost:4242', secure: false, }, + '/metrics': { + target: 'http://localhost:4242', + secure: false, + }, }, }, };