diff --git a/frontend/src/component/common/select.jsx b/frontend/src/component/common/select.jsx
index facc3eefb6..fd193c55a4 100644
--- a/frontend/src/component/common/select.jsx
+++ b/frontend/src/component/common/select.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-const Select = ({ name, value, label, options, style, onChange }) => {
+const Select = ({ name, value, label, options, style, onChange, filled }) => {
const wrapper = Object.assign({ width: 'auto' }, style);
return (
{
name={name}
onChange={onChange}
value={value}
- style={{ width: 'auto' }}
+ style={{ width: 'auto', background: filled ? '#f5f5f5' : 'none' }}
>
{options.map(o => (
-
))}
diff --git a/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap b/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap
index 8e1aa06601..9e510db3ea 100644
--- a/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap
+++ b/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap
@@ -47,10 +47,8 @@ exports[`renders correctly with one feature 1`] = `
className="listItemStrategies hideLt920"
>
- gradualRolloutRandom
-
+ className="mdl-color--blue-grey-100"
+ />
@@ -102,10 +100,8 @@ exports[`renders correctly with one feature without permission 1`] = `
className="listItemStrategies hideLt920"
>
- gradualRolloutRandom
-
+ className="mdl-color--blue-grey-100"
+ />
diff --git a/frontend/src/component/feature/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/feature/__tests__/__snapshots__/list-component-test.jsx.snap
index d5bbbd92ed..a9045b9eac 100644
--- a/frontend/src/component/feature/__tests__/__snapshots__/list-component-test.jsx.snap
+++ b/frontend/src/component/feature/__tests__/__snapshots__/list-component-test.jsx.snap
@@ -124,6 +124,12 @@ exports[`renders correctly with one feature 1`] = `
>
Name
+
+ Type
+
Name
+
+ Type
+
- another's description
-
-
- edit
-
+
+
+
+
({
__esModule: true,
default: 'UpdateFeatureToggleComponent',
}));
+jest.mock('../form/feature-type-select-container', () => 'FeatureTypeSelect');
test('renders correctly with one feature', () => {
const feature = {
name: 'Another',
description: "another's description",
enabled: false,
+ type: 'release',
strategies: [
{
name: 'gradualRolloutRandom',
diff --git a/frontend/src/component/feature/feature-list-item-component.jsx b/frontend/src/component/feature/feature-list-item-component.jsx
index e19ab683b6..a0a0435491 100644
--- a/frontend/src/component/feature/feature-list-item-component.jsx
+++ b/frontend/src/component/feature/feature-list-item-component.jsx
@@ -17,7 +17,7 @@ const Feature = ({
revive,
hasPermission,
}) => {
- const { name, description, enabled, strategies } = feature;
+ const { name, description, enabled, type } = feature;
const { showLastHour = false } = settings;
const isStale = showLastHour ? metricsLastHour.isFallback : metricsLastMinute.isFallback;
const percent =
@@ -25,17 +25,7 @@ const Feature = ({
(showLastHour
? calc(metricsLastHour.yes, metricsLastHour.yes + metricsLastHour.no, 0)
: calc(metricsLastMinute.yes, metricsLastMinute.yes + metricsLastMinute.no, 0));
-
- const strategiesToShow = Math.min(strategies.length, 3);
- const remainingStrategies = strategies.length - strategiesToShow;
- const strategyChips =
- strategies &&
- strategies.slice(0, strategiesToShow).map((s, i) => (
-
- {s.name}
-
- ));
- const summaryChip = remainingStrategies > 0 && +{remainingStrategies};
+ const typeChip = {type};
const featureUrl = toggleFeature === undefined ? `/archive/strategies/${name}` : `/features/strategies/${name}`;
return (
@@ -61,10 +51,7 @@ const Feature = ({
{description}
-
- {strategyChips}
- {summaryChip}
-
+ {typeChip}
{revive && hasPermission(UPDATE_FEATURE) ? (
revive(feature.name)}>
diff --git a/frontend/src/component/feature/feature.scss b/frontend/src/component/feature/feature.scss
index 05730919e3..bba65140ce 100644
--- a/frontend/src/component/feature/feature.scss
+++ b/frontend/src/component/feature/feature.scss
@@ -33,3 +33,8 @@
.strategyChip {
margin-left: 8px !important;
}
+
+.typeChip {
+ margin-left: 8px !important;
+ background: #d3c1ff;
+}
\ No newline at end of file
diff --git a/frontend/src/component/feature/form/__tests__/__snapshots__/form-add-feature-component-test.jsx.snap b/frontend/src/component/feature/form/__tests__/__snapshots__/form-add-feature-component-test.jsx.snap
index d721fdbf24..98a882c18e 100644
--- a/frontend/src/component/feature/form/__tests__/__snapshots__/form-add-feature-component-test.jsx.snap
+++ b/frontend/src/component/feature/form/__tests__/__snapshots__/form-add-feature-component-test.jsx.snap
@@ -23,21 +23,54 @@ exports[`render the create feature page 1`] = `
);
}
diff --git a/frontend/src/component/feature/list-component.jsx b/frontend/src/component/feature/list-component.jsx
index 55b6f1c634..ea9d5c3e29 100644
--- a/frontend/src/component/feature/list-component.jsx
+++ b/frontend/src/component/feature/list-component.jsx
@@ -105,6 +105,9 @@ export default class FeatureListComponent extends React.Component {
+
diff --git a/frontend/src/component/feature/list-container.jsx b/frontend/src/component/feature/list-container.jsx
index 49a1150a9c..54b5733a92 100644
--- a/frontend/src/component/feature/list-container.jsx
+++ b/frontend/src/component/feature/list-container.jsx
@@ -47,6 +47,16 @@ export const mapStateToPropsConfigurable = isFeature => state => {
});
} else if (settings.sort === 'strategies') {
features = features.sort((a, b) => (a.strategies.length > b.strategies.length ? -1 : 1));
+ } else if (settings.sort === 'type') {
+ features = features.sort((a, b) => {
+ if (a.type < b.type) {
+ return -1;
+ }
+ if (a.type > b.type) {
+ return 1;
+ }
+ return 0;
+ });
} else if (settings.sort === 'metrics') {
const target = settings.showLastHour ? featureMetrics.lastHour : featureMetrics.lastMinute;
diff --git a/frontend/src/component/feature/view-component.jsx b/frontend/src/component/feature/view-component.jsx
index f656347662..37ac1cf42d 100644
--- a/frontend/src/component/feature/view-component.jsx
+++ b/frontend/src/component/feature/view-component.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Tabs, Tab, ProgressBar, Button, Card, CardTitle, CardActions, Switch } from 'react-mdl';
+import { Tabs, Tab, ProgressBar, Button, Card, CardTitle, CardActions, Switch, CardText } from 'react-mdl';
import { Link } from 'react-router-dom';
import HistoryComponent from '../history/history-list-toggle-container';
@@ -8,6 +8,7 @@ import MetricComponent from './metric-container';
import EditFeatureToggle from './form/form-update-feature-container';
import EditVariants from './variant/update-variant-container';
import ViewFeatureToggle from './form/form-view-feature-container';
+import FeatureTypeSelect from './form/feature-type-select-container';
import UpdateDescriptionComponent from './form/update-description-component';
import { styles as commonStyles } from '../common';
import { CREATE_FEATURE, DELETE_FEATURE, UPDATE_FEATURE } from '../../permissions';
@@ -145,16 +146,33 @@ export default class ViewFeatureToggleComponent extends React.Component {
this.props.editFeatureToggle(feature);
};
+ const updateType = evt => {
+ evt.preventDefault();
+ const type = evt.target.value;
+ let feature = { ...featureToggle, type };
+ if (Array.isArray(feature.strategies)) {
+ feature.strategies.forEach(s => {
+ delete s.id;
+ });
+ }
+
+ this.props.editFeatureToggle(feature);
+ };
return (
{featureToggle.name}
-
+
+
+
+
+
+
response.json());
+}
+
+export default {
+ fetchAll,
+};
diff --git a/frontend/src/store/error-store.js b/frontend/src/store/error-store.js
index 19f61dc77c..c172f1fe9a 100644
--- a/frontend/src/store/error-store.js
+++ b/frontend/src/store/error-store.js
@@ -6,6 +6,7 @@ import {
ERROR_REMOVE_FEATURE_TOGGLE,
ERROR_UPDATE_FEATURE_TOGGLE,
UPDATE_FEATURE_TOGGLE_STRATEGIES,
+ UPDATE_FEATURE_TOGGLE,
} from './feature-actions';
import { ERROR_UPDATING_STRATEGY, ERROR_CREATING_STRATEGY, ERROR_RECEIVE_STRATEGIES } from './strategy/actions';
@@ -46,6 +47,7 @@ const strategies = (state = getInitState(), action) => {
return addErrorIfNotAlreadyInList(state, action.error.message || '403 Forbidden');
case MUTE_ERROR:
return state.update('list', list => list.remove(list.indexOf(action.error)));
+ case UPDATE_FEATURE_TOGGLE:
case UPDATE_FEATURE_TOGGLE_STRATEGIES:
return addErrorIfNotAlreadyInList(state, action.info);
default:
diff --git a/frontend/src/store/feature-actions.js b/frontend/src/store/feature-actions.js
index be60edbbde..5ad8d57bd6 100644
--- a/frontend/src/store/feature-actions.js
+++ b/frontend/src/store/feature-actions.js
@@ -82,7 +82,11 @@ export function requestUpdateFeatureToggle(featureToggle) {
return api
.update(featureToggle)
- .then(() => dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle }))
+ .then(() => {
+ const info = `${featureToggle.name} successfully updated!`;
+ setTimeout(() => dispatch({ type: MUTE_ERROR, error: info }), 1000);
+ dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle, info });
+ })
.catch(dispatchAndThrow(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
};
}
diff --git a/frontend/src/store/feature-type/actions.js b/frontend/src/store/feature-type/actions.js
new file mode 100644
index 0000000000..04eaaeae75
--- /dev/null
+++ b/frontend/src/store/feature-type/actions.js
@@ -0,0 +1,15 @@
+import api from '../../data/feature-type-api';
+import { dispatchAndThrow } from '../util';
+
+export const RECEIVE_FEATURE_TYPES = 'RECEIVE_FEATURE_TYPES';
+export const ERROR_RECEIVE_FEATURE_TYPES = 'ERROR_RECEIVE_FEATURE_TYPES';
+
+const receiveFeatureTypes = value => ({ type: RECEIVE_FEATURE_TYPES, value });
+
+export function fetchFeatureTypes() {
+ return dispatch =>
+ api
+ .fetchAll()
+ .then(json => dispatch(receiveFeatureTypes(json)))
+ .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_FEATURE_TYPES));
+}
diff --git a/frontend/src/store/feature-type/index.js b/frontend/src/store/feature-type/index.js
new file mode 100644
index 0000000000..32fdb97851
--- /dev/null
+++ b/frontend/src/store/feature-type/index.js
@@ -0,0 +1,19 @@
+import { List } from 'immutable';
+import { RECEIVE_FEATURE_TYPES } from './actions';
+
+const DEFAULT_FEATURE_TYPES = [{ id: 'release', name: 'Release', inital: true }];
+
+function getInitState() {
+ return new List(DEFAULT_FEATURE_TYPES);
+}
+
+const strategies = (state = getInitState(), action) => {
+ switch (action.type) {
+ case RECEIVE_FEATURE_TYPES:
+ return new List(action.value.types);
+ default:
+ return state;
+ }
+};
+
+export default strategies;
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index f1fd1f157a..d7fc5efe1a 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -1,5 +1,6 @@
import { combineReducers } from 'redux';
import features from './feature-store';
+import featureTypes from './feature-type';
import featureMetrics from './feature-metrics-store';
import strategies from './strategy';
import input from './input-store';
@@ -15,6 +16,7 @@ import context from './context';
const unleashStore = combineReducers({
features,
+ featureTypes,
featureMetrics,
strategies,
input,