From 61dd7680a4fba6eed1133b3a788fe54fe1544821 Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Mon, 19 Dec 2016 20:53:49 +0100 Subject: [PATCH] Feature Toggle View: reafactored tabs to be url based. --- .../feature/feature-list-item-component.jsx | 4 +- .../component/feature/form-edit-container.jsx | 4 +- .../component/feature/metric-component.jsx | 124 ++++++++++-------- .../component/feature/metric-container.jsx | 32 +++++ .../src/component/feature/view-component.jsx | 70 +++++----- .../src/component/feature/view-container.jsx | 14 ++ .../component/feature/view-edit-container.jsx | 57 -------- frontend/src/index.jsx | 4 +- frontend/src/page/features/edit.js | 16 --- frontend/src/page/features/show.js | 17 +++ 10 files changed, 176 insertions(+), 166 deletions(-) create mode 100644 frontend/src/component/feature/metric-container.jsx create mode 100644 frontend/src/component/feature/view-container.jsx delete mode 100644 frontend/src/component/feature/view-edit-container.jsx delete mode 100644 frontend/src/page/features/edit.js create mode 100644 frontend/src/page/features/show.js diff --git a/frontend/src/component/feature/feature-list-item-component.jsx b/frontend/src/component/feature/feature-list-item-component.jsx index 815dc32516..40436c5242 100644 --- a/frontend/src/component/feature/feature-list-item-component.jsx +++ b/frontend/src/component/feature/feature-list-item-component.jsx @@ -42,7 +42,7 @@ const Feature = ({ onFeatureClick(feature)} checked={enabled} /> - + {name} {shorten(description, 30) || ''} @@ -54,7 +54,7 @@ const Feature = ({ - + onFeatureRemove(name)} className={style.iconListItem} /> diff --git a/frontend/src/component/feature/form-edit-container.jsx b/frontend/src/component/feature/form-edit-container.jsx index 44a7b62fce..7f592804f8 100644 --- a/frontend/src/component/feature/form-edit-container.jsx +++ b/frontend/src/component/feature/form-edit-container.jsx @@ -27,14 +27,14 @@ const prepare = (methods, dispatch) => { // TODO: should add error handling requestUpdateFeatureToggle(input)(dispatch) .then(() => methods.clear()) - .then(() => window.history.back()); + .then(() => hashHistory.push(`/features/view/${input.name}`)); } ); methods.onCancel = (evt) => { evt.preventDefault(); methods.clear(); - hashHistory.push('/features'); + window.history.back(); }; methods.addStrategy = (v) => { diff --git a/frontend/src/component/feature/metric-component.jsx b/frontend/src/component/feature/metric-component.jsx index 824fa0ab23..a7923dcc4d 100644 --- a/frontend/src/component/feature/metric-component.jsx +++ b/frontend/src/component/feature/metric-component.jsx @@ -3,64 +3,80 @@ import { Grid, Cell, Icon } from 'react-mdl'; import Progress from './progress'; import { AppsLinkList, SwitchWithLabel, calc } from '../common'; -const MetricComponent = ({ metrics, featureToggle, toggleFeature }) => { - const { + +export default class MetricComponent extends React.Component { + static propTypes () { + return { + metrics: PropTypes.object.isRequired, + featureToggle: PropTypes.object.isRequired, + toggleFeature: PropTypes.func.isRequired, + fetchSeenApps: PropTypes.func.isRequired, + fetchFeatureMetrics: PropTypes.func.isRequired, + }; + } + + componentWillMount () { + this.props.fetchSeenApps(); + this.props.fetchFeatureMetrics(); + this.timer = setInterval(() => { + this.props.fetchFeatureMetrics(); + }, 5000); + } + + componentWillUnmount () { + clearInterval(this.timer); + } + + render () { + const { metrics = {}, featureToggle, toggleFeature } = this.props; + const { lastHour = { yes: 0, no: 0, isFallback: true }, lastMinute = { yes: 0, no: 0, isFallback: true }, seenApps = [], } = metrics; - const lastHourPercent = 1 * calc(lastHour.yes, lastHour.yes + lastHour.no, 0); - const lastMinutePercent = 1 * calc(lastMinute.yes, lastMinute.yes + lastMinute.no, 0); + const lastHourPercent = 1 * calc(lastHour.yes, lastHour.yes + lastHour.no, 0); + const lastMinutePercent = 1 * calc(lastMinute.yes, lastMinute.yes + lastMinute.no, 0); - return (
- toggleFeature(featureToggle)}>Toggle {featureToggle.name} -
- - - { - lastMinute.isFallback ? - : -
- -
- } -

Last minute
Yes {lastMinute.yes}, No: {lastMinute.no}

-
- - { - lastHour.isFallback ? - : -
- -
- } -

Last hour
Yes {lastHour.yes}, No: {lastHour.no}

-
- - {seenApps.length > 0 ? - (
Seen in applications:
) : -
+ return (
+ toggleFeature(featureToggle)}>Toggle {featureToggle.name} +
+ + + { + lastMinute.isFallback ? -
Not used in a app in the last hour. - This might be due to your client implementation is not reporting usage.
-
- } - - - -
); -}; - -MetricComponent.propTypes = { - metrics: PropTypes.object, - featureToggle: PropTypes.object, - toggleFeature: PropTypes.object, -}; - -export default MetricComponent; + name="report problem" title="No metrics avaiable" /> : +
+ +
+ } +

Last minute
Yes {lastMinute.yes}, No: {lastMinute.no}

+
+ + { + lastHour.isFallback ? + : +
+ +
+ } +

Last hour
Yes {lastHour.yes}, No: {lastHour.no}

+
+ + {seenApps.length > 0 ? + (
Seen in applications:
) : +
+ +
Not used in a app in the last hour. + This might be due to your client implementation is not reporting usage.
+
+ } + +
+
+
); + } +} diff --git a/frontend/src/component/feature/metric-container.jsx b/frontend/src/component/feature/metric-container.jsx new file mode 100644 index 0000000000..89bfff15bc --- /dev/null +++ b/frontend/src/component/feature/metric-container.jsx @@ -0,0 +1,32 @@ + +import { connect } from 'react-redux'; + +import { fetchFeatureMetrics, fetchSeenApps } from '../../store/feature-metrics-actions'; +import { toggleFeature } from '../../store/feature-actions'; + +import MatricComponent from './metric-component'; + +function getMetricsForToggle (state, toggleName) { + if (!toggleName) { + return; + } + const result = {}; + + if (state.featureMetrics.hasIn(['seenApps', toggleName])) { + result.seenApps = state.featureMetrics.getIn(['seenApps', toggleName]); + } + if (state.featureMetrics.hasIn(['lastHour', toggleName])) { + result.lastHour = state.featureMetrics.getIn(['lastHour', toggleName]); + result.lastMinute = state.featureMetrics.getIn(['lastMinute', toggleName]); + } + return result; +} + + +export default connect((state, props) => ({ + metrics: getMetricsForToggle(state, props.featureToggleName), +}), { + fetchFeatureMetrics, + toggleFeature, + fetchSeenApps, +})(MatricComponent); diff --git a/frontend/src/component/feature/view-component.jsx b/frontend/src/component/feature/view-component.jsx index 49350512b5..17d1163a6f 100644 --- a/frontend/src/component/feature/view-component.jsx +++ b/frontend/src/component/feature/view-component.jsx @@ -1,25 +1,30 @@ import React, { PropTypes } from 'react'; -import { Tabs, Tab, ProgressBar, List, ListItem, ListItemContent } from 'react-mdl'; -import { Link } from 'react-router'; +import { Tabs, Tab, ProgressBar } from 'react-mdl'; +import { hashHistory } from 'react-router'; import HistoryComponent from '../history/history-list-toggle-container'; -import MetricComponent from './metric-component'; +import MetricComponent from './metric-container'; import EditFeatureToggle from './form-edit-container.jsx'; -import { getIcon } from '../common'; + +const TABS = { + view: 0, + edit: 1, + history: 2, +}; export default class ViewFeatureToggleComponent extends React.Component { constructor (props) { super(props); - - this.state = { activeTab: 0 }; } static propTypes () { return { + activeTab: PropTypes.string.isRequired, featureToggleName: PropTypes.string.isRequired, features: PropTypes.array.isRequired, fetchFeatureToggles: PropTypes.array.isRequired, + featureToggle: PropTypes.object.isRequired, }; } @@ -27,42 +32,44 @@ export default class ViewFeatureToggleComponent extends React.Component { if (this.props.features.length === 0) { this.props.fetchFeatureToggles(); } - this.props.fetchSeenApps(); - this.props.fetchFeatureMetrics(); - this.timer = setInterval(() => { - this.props.fetchFeatureMetrics(); - }, 5000); } - componentWillUnmount () { - clearInterval(this.timer); + getTabContent (activeTab) { + const { + featureToggle, + featureToggleName, + } = this.props; + + if (TABS[activeTab] === TABS.history) { + return ; + } else if (TABS[activeTab] === TABS.edit) { + return ; + } else { + return ; + } + } + + goToTab (tabName, featureToggleName) { + hashHistory.push(`/features/${tabName}/${featureToggleName}`); } render () { const { - toggleFeature, + featureToggle, features, + activeTab, featureToggleName, - metrics = {}, } = this.props; - const featureToggle = features.find(toggle => toggle.name === featureToggleName); - if (!featureToggle) { if (features.length === 0 ) { return ; } - return Could not find the toggle "{this.props.featureToggleName}"; + return Could not find the toggle "{featureToggleName}"; } - let tabContent; - if (this.state.activeTab === 0) { - tabContent = ; - } else if (this.state.activeTab === 1) { - tabContent = ; - } else { - tabContent = ; - } + const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.view; + const tabContent = this.getTabContent(activeTab); return (
@@ -72,13 +79,10 @@ export default class ViewFeatureToggleComponent extends React.Component {
{featureToggle.description}
- this.setState({ activeTab: tabId })} - ripple - style={{ marginBottom: '10px' }}> - Metrics - Edit - History + + this.goToTab('view', featureToggleName)}>Metrics + this.goToTab('edit', featureToggleName)}>Edit + this.goToTab('history', featureToggleName)}>History {tabContent} diff --git a/frontend/src/component/feature/view-container.jsx b/frontend/src/component/feature/view-container.jsx new file mode 100644 index 0000000000..47bedf154a --- /dev/null +++ b/frontend/src/component/feature/view-container.jsx @@ -0,0 +1,14 @@ + +import { connect } from 'react-redux'; + +import { fetchFeatureToggles } from '../../store/feature-actions'; + +import ViewToggleComponent from './view-component'; + +export default connect((state, props) => ({ + features: state.features.toJS(), + featureToggle: state.features.toJS().find(toggle => toggle.name === props.featureToggleName), + activeTab: props.activeTab, +}), { + fetchFeatureToggles, +})(ViewToggleComponent); diff --git a/frontend/src/component/feature/view-edit-container.jsx b/frontend/src/component/feature/view-edit-container.jsx deleted file mode 100644 index 5fbb9b6b65..0000000000 --- a/frontend/src/component/feature/view-edit-container.jsx +++ /dev/null @@ -1,57 +0,0 @@ - -import { connect } from 'react-redux'; - -import { fetchFeatureToggles, toggleFeature } from '../../store/feature-actions'; -import { fetchFeatureMetrics, fetchSeenApps } from '../../store/feature-metrics-actions'; -import { fetchHistoryForToggle } from '../../store/history-actions'; - -import ViewToggleComponent from './view-component'; - -function getMetricsForToggle (state, toggleName) { - if (!toggleName) { - return; - } - const result = {}; - - if (state.featureMetrics.hasIn(['seenApps', toggleName])) { - result.seenApps = state.featureMetrics.getIn(['seenApps', toggleName]); - } - if (state.featureMetrics.hasIn(['lastHour', toggleName])) { - result.lastHour = state.featureMetrics.getIn(['lastHour', toggleName]); - result.lastMinute = state.featureMetrics.getIn(['lastMinute', toggleName]); - } - return result; -} - -function getHistoryFromToggle (state, toggleName) { - if (!toggleName) { - return []; - } - - if (state.history.hasIn(['toggles', toggleName])) { - return state.history - .getIn(['toggles', toggleName]) - .slice(0, 10) - .toJS() - .map(({ createdAt, createdBy, type }) => ({ - createdAt: new Date(createdAt).toLocaleString('nb-NO'), - createdBy, - type, - })); - } - - return []; -} - - -export default connect((state, props) => ({ - features: state.features.toJS(), - metrics: getMetricsForToggle(state, props.featureToggleName), - history: getHistoryFromToggle(state, props.featureToggleName), -}), { - fetchFeatureMetrics, - fetchFeatureToggles, - toggleFeature, - fetchSeenApps, - fetchHistoryForToggle, -})(ViewToggleComponent); diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 35ea590100..4d14854bc9 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -11,7 +11,7 @@ import App from './component/app'; import Features from './page/features'; import CreateFeatureToggle from './page/features/create'; -import EditFeatureToggle from './page/features/edit'; +import ViewFeatureToggle from './page/features/show'; import Strategies from './page/strategies'; import StrategyView from './page/strategies/show'; import CreateStrategies from './page/strategies/create'; @@ -39,7 +39,7 @@ ReactDOM.render( - + diff --git a/frontend/src/page/features/edit.js b/frontend/src/page/features/edit.js deleted file mode 100644 index 5f0cd3a4b3..0000000000 --- a/frontend/src/page/features/edit.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component, PropTypes } from 'react'; -import EditFeatureToggleForm from '../../component/feature/view-edit-container'; - -export default class Features extends Component { - static propTypes () { - return { - params: PropTypes.object.isRequired, - }; - } - - render () { - return ( - - ); - } -}; diff --git a/frontend/src/page/features/show.js b/frontend/src/page/features/show.js new file mode 100644 index 0000000000..7cff11b9f1 --- /dev/null +++ b/frontend/src/page/features/show.js @@ -0,0 +1,17 @@ +import React, { PureComponent, PropTypes } from 'react'; +import ViewFeatureToggle from '../../component/feature/view-container'; + +export default class Features extends PureComponent { + static propTypes () { + return { + params: PropTypes.object.isRequired, + }; + } + + render () { + const { params } = this.props; + return ( + + ); + } +};