diff --git a/packages/unleash-frontend-next/src/component/feature/add-component.jsx b/packages/unleash-frontend-next/src/component/feature/add-component.jsx
index 58ec139294..6698c25781 100644
--- a/packages/unleash-frontend-next/src/component/feature/add-component.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/add-component.jsx
@@ -1,78 +1,88 @@
-import React, { PropTypes } from 'react';
+import React, { Component, PropTypes } from 'react';
import { Input, Switch, Button } from 'react-toolbox';
-import AddFeatureToggleStrategy from './strategies-for-toggle';
+import SelectStrategies from './strategies-for-toggle';
import SelectedStrategies from './selected-strategies';
-const AddFeatureToggleComponent = ({
- strategies,
- featureToggle,
- updateField,
- addStrategy,
- removeStrategy,
- onSubmit,
- onCancel,
- editmode,
-}) => {
- const {
- name, // eslint-disable-line
- description,
- enabled,
- } = featureToggle;
- const configuredStrategies = featureToggle.strategies;
+class AddFeatureToggleComponent extends Component {
- return (
-
- );
};
AddFeatureToggleComponent.propTypes = {
strategies: PropTypes.array.required,
- featureToggle: PropTypes.object,
- updateField: PropTypes.func.required,
+ input: PropTypes.object,
+ setValue: PropTypes.func.required,
addStrategy: PropTypes.func.required,
removeStrategy: PropTypes.func.required,
onSubmit: PropTypes.func.required,
diff --git a/packages/unleash-frontend-next/src/component/feature/add-container.jsx b/packages/unleash-frontend-next/src/component/feature/add-container.jsx
index 015c66f60b..7f43851213 100644
--- a/packages/unleash-frontend-next/src/component/feature/add-container.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/add-container.jsx
@@ -1,80 +1,36 @@
-import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
+
import { createFeatureToggles } from '../../store/feature-actions';
-import { fetchStrategies } from '../../store/strategy-actions';
-import AddFeatureToggleUI from './add-component';
+import AddComponent from './add-component';
+import { createMapper, createActions } from '../input-helpers';
-class AddFeatureToggle extends React.Component {
- constructor () {
- super();
- this.state = {
- name: '',
- description: '',
- enabled: false,
- strategies: [],
- };
- }
+const ID = 'add-feature-toggle';
+const mapStateToProps = createMapper({ id: ID });
+const prepare = (methods, dispatch) => {
+ methods.onSubmit = (input) => (
+ (e) => {
+ e.preventDefault();
+ // TODO: should add error handling
+ createFeatureToggles(input)(dispatch)
+ .then(() => methods.clear())
+ .then(() => window.history.back());
+ }
+ );
- static propTypes () {
- return {
- dispatch: PropTypes.func.isRequired,
- strategies: PropTypes.array,
- };
- }
-
- static contextTypes = {
- router: React.PropTypes.object,
- }
-
- onSubmit = (evt) => {
- evt.preventDefault();
- this.props.dispatch(createFeatureToggles(this.state));
- this.context.router.push('/features');
+ methods.onCancel = () => {
+ window.history.back();
};
- onCancel = (evt) => {
- evt.preventDefault();
- this.context.router.push('/features');
+ methods.addStrategy = (v) => {
+ methods.pushToList('strategies', v);
};
- updateField = (key, value) => {
- const change = {};
- change[key] = value;
- this.setState(change);
+ methods.removeStrategy = (v) => {
+ methods.removeFromList('strategies', v);
};
- addStrategy = (strategy) => {
- const strategies = this.state.strategies;
- strategies.push(strategy);
- this.setState({ strategies });
- }
+ return methods;
+};
+const actions = createActions({ id: ID, prepare });
- removeStrategy = (strategy) => {
- const strategies = this.state.strategies.filter(s => s !== strategy);
- this.setState({ strategies });
- }
-
- componentDidMount () {
- this.props.fetchStrategies();
- }
-
- render () {
- return (
-
- );
- }
-}
-
-const mapStateToProps = (state) => ({
- strategies: state.strategies.get('list').toArray(),
-});
-
-export default connect(mapStateToProps, { fetchStrategies })(AddFeatureToggle);
+export default connect(mapStateToProps, actions)(AddComponent);
diff --git a/packages/unleash-frontend-next/src/component/feature/edit-container.jsx b/packages/unleash-frontend-next/src/component/feature/edit-container.jsx
index 0fed095c7f..492d0bcf6d 100644
--- a/packages/unleash-frontend-next/src/component/feature/edit-container.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/edit-container.jsx
@@ -1,86 +1,62 @@
-import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
-import { editFeatureToggle } from '../../store/feature-actions';
-import AddFeatureToggleComponent from './add-component';
-import { fetchStrategies } from '../../store/strategy-actions';
+import { requestUpdateFeatureToggle } from '../../store/feature-actions';
+import AddComponent from './add-component';
+import { createMapper, createActions } from '../input-helpers';
-const mapStateToProps = (state, ownProps) => ({
- strategies: state.strategies.get('list').toArray(),
- featureToggle: state.features.toJS().find(toggle => toggle.name === ownProps.featureToggleName) || {},
+const ID = 'edit-feature-toggle';
+function getId (props) {
+ return [ID, props.featureToggleName];
+}
+// TODO: need to scope to the active featureToggle
+// best is to emulate the "input-storage"?
+const mapStateToProps = createMapper({
+ id: getId,
+ getDefault: (state, ownProps) => {
+ if (ownProps.featureToggleName) {
+ const match = state.features.findEntry((entry) => entry.get('name') === ownProps.featureToggleName);
+
+ if (match && match[1]) {
+ return match[1].toJS();
+ }
+ }
+ return {};
+ },
+ prepare: (props) => {
+ props.editmode = true;
+ return props;
+ },
});
-class EditFeatureToggle extends React.Component {
- constructor (props) {
- super(props);
- this.state = {
- name: props.featureToggle.name || '',
- description: props.featureToggle.description || '',
- enabled: props.featureToggle.enabled || false,
- strategies: props.featureToggle.strategies || [],
- };
- }
+const prepare = (methods, dispatch) => {
+ methods.onSubmit = (input) => (
+ (e) => {
+ e.preventDefault();
+ // TODO: should add error handling
+ requestUpdateFeatureToggle(input)(dispatch)
+ .then(() => methods.clear())
+ .then(() => window.history.back());
+ }
+ );
- static propTypes () {
- return {
- dispatch: PropTypes.func.isRequired,
- strategies: PropTypes.array,
- featureToggle: PropTypes.featureToggle.isRequired,
- fetchFeatureToggles: PropTypes.func.isRequired,
- };
- }
-
- componentDidMount () {
- // todo fetch feature if missing? (reload of page does not fetch data from url)
- this.props.fetchStrategies();
- }
-
- static contextTypes = {
- router: React.PropTypes.object,
- }
-
- onSubmit = (evt) => {
- evt.preventDefault();
- this.props.dispatch(editFeatureToggle(this.state));
- this.context.router.push('/features');
+ methods.onCancel = () => {
+ window.history.back();
};
- onCancel = (evt) => {
- evt.preventDefault();
- this.context.router.push('/features');
+ methods.addStrategy = (v) => {
+ methods.pushToList('strategies', v);
};
- updateField = (key, value) => {
- const change = {};
- change[key] = value;
- this.setState(change);
+ methods.removeStrategy = (v) => {
+ methods.removeFromList('strategies', v);
};
- addStrategy = (strategy) => {
- const strategies = this.state.strategies;
- strategies.push(strategy);
- this.setState({ strategies });
- }
+ return methods;
+};
- removeStrategy = (strategy) => {
- const strategies = this.state.strategies.filter(s => s !== strategy);
- this.setState({ strategies });
- }
+const actions = createActions({
+ id: getId,
+ prepare,
+});
- render () {
- return (
-
- );
- }
-}
-
-export default connect(mapStateToProps, { fetchStrategies })(EditFeatureToggle);
+export default connect(mapStateToProps, actions)(AddComponent);
diff --git a/packages/unleash-frontend-next/src/component/feature/select-strategies-container.js b/packages/unleash-frontend-next/src/component/feature/select-strategies-container.js
new file mode 100644
index 0000000000..95489c2f57
--- /dev/null
+++ b/packages/unleash-frontend-next/src/component/feature/select-strategies-container.js
@@ -0,0 +1,8 @@
+import { connect } from 'react-redux';
+import SelectStrategies from './select-strategies';
+import { fetchStrategies } from '../../store/strategy-actions';
+
+
+export default connect((state) => ({
+ strategies: state.strategies.get('list').toArray(),
+}), { fetchStrategies })(SelectStrategies);
diff --git a/packages/unleash-frontend-next/src/component/feature/select-strategies.jsx b/packages/unleash-frontend-next/src/component/feature/select-strategies.jsx
index bd1a9ceab7..e5c6b62a06 100644
--- a/packages/unleash-frontend-next/src/component/feature/select-strategies.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/select-strategies.jsx
@@ -18,6 +18,17 @@ class SelectStrategies extends React.Component {
};
}
+ componentWillMount () {
+ this.props.fetchStrategies();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ // this will fix async strategies list loading after mounted
+ if (!this.state.selectedStrategy && nextProps.strategies.length > 0) {
+ this.setState({ selectedStrategy: nextProps.strategies[0] });
+ }
+ }
+
handleChange = (evt) => {
const strategyName = evt.target.value;
const selectedStrategy = this.props.strategies.find(s => s.name === strategyName);
@@ -63,7 +74,11 @@ class SelectStrategies extends React.Component {
padding: '10px',
};
- const selectedStrategy = this.state.selectedStrategy;
+ const selectedStrategy = this.state.selectedStrategy || this.props.strategies[0];
+
+ if (!selectedStrategy) {
+ return Strategies loading...
;
+ }
return (
diff --git a/packages/unleash-frontend-next/src/component/feature/selected-strategies.jsx b/packages/unleash-frontend-next/src/component/feature/selected-strategies.jsx
index 0de1b28941..885e248360 100644
--- a/packages/unleash-frontend-next/src/component/feature/selected-strategies.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/selected-strategies.jsx
@@ -19,7 +19,7 @@ class SelectedStrategies extends React.Component {
render () {
const removeStrategy = this.props.removeStrategy;
- const strategies = this.props.configuredStrategies.map((s, index) => (
+ const configuredStrategies = this.props.configuredStrategies.map((s, index) => (
- {strategies.length > 0 ? strategies : No activation strategies added
}
+ {configuredStrategies.length > 0 ? configuredStrategies : No activation strategies added
}
);
}
diff --git a/packages/unleash-frontend-next/src/component/feature/strategies-for-toggle.jsx b/packages/unleash-frontend-next/src/component/feature/strategies-for-toggle.jsx
index 397deb2c42..caa3a0939b 100644
--- a/packages/unleash-frontend-next/src/component/feature/strategies-for-toggle.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/strategies-for-toggle.jsx
@@ -1,5 +1,5 @@
import React, { PropTypes } from 'react';
-import SelectStrategies from './select-strategies';
+import SelectStrategies from './select-strategies-container';
class AddStrategiesToToggle extends React.Component {
constructor () {
@@ -11,7 +11,6 @@ class AddStrategiesToToggle extends React.Component {
static propTypes () {
return {
- strategies: PropTypes.array.isRequired,
addStrategy: PropTypes.func.isRequired,
};
}
@@ -39,12 +38,11 @@ class AddStrategiesToToggle extends React.Component {
}
render () {
- return this.state.showConfigure ?
- :
- this.renderAddLink();
+ return (
+ this.state.showConfigure ?
+ :
+ this.renderAddLink()
+ );
}
}
diff --git a/packages/unleash-frontend-next/src/component/input-helpers.js b/packages/unleash-frontend-next/src/component/input-helpers.js
index 2200669156..53ecaa6fc8 100644
--- a/packages/unleash-frontend-next/src/component/input-helpers.js
+++ b/packages/unleash-frontend-next/src/component/input-helpers.js
@@ -1,32 +1,63 @@
-import { createInc, createClear, createSet } from '../store/input-actions';
+import {
+ createInc,
+ createClear,
+ createSet,
+ createPop,
+ createPush,
+ createInit,
+} from '../store/input-actions';
-export function createMapper (id, prepare = (v) => v) {
- return (state) => {
+function getId (id, ownProps) {
+ if (typeof id === 'function') {
+ return id(ownProps); // should return array...
+ }
+ return [id];
+}
+
+export function createMapper ({ id, getDefault, prepare = (v) => v }) {
+ return (state, ownProps) => {
let input;
- if (state.input.has(id)) {
- input = state.input.get(id).toJS();
+ let initCallRequired = false;
+ const scope = getId(id, ownProps);
+ if (state.input.hasIn(scope)) {
+ input = state.input.getIn(scope).toJS();
} else {
- input = {};
+ initCallRequired = true;
+ input = getDefault ? getDefault(state, ownProps) : {};
}
return prepare({
+ initCallRequired,
input,
- }, state);
+ }, state, ownProps);
};
}
-export function createActions (id, prepare = (v) => v) {
- return (dispatch) => (prepare({
+export function createActions ({ id, prepare = (v) => v }) {
+ return (dispatch, ownProps) => (prepare({
+
clear () {
- dispatch(createClear({ id }));
+ dispatch(createClear({ id: getId(id, ownProps) }));
+ },
+
+ init (value) {
+ dispatch(createInit({ id: getId(id, ownProps), value }));
},
setValue (key, value) {
- dispatch(createSet({ id, key, value }));
+ dispatch(createSet({ id: getId(id, ownProps), key, value }));
+ },
+
+ pushToList (key, value) {
+ dispatch(createPush({ id: getId(id, ownProps), key, value }));
+ },
+
+ removeFromList (key, value) {
+ dispatch(createPop({ id: getId(id, ownProps), key, value }));
},
incValue (key) {
- dispatch(createInc({ id, key }));
+ dispatch(createInc({ id: getId(id, ownProps), key }));
},
- }, dispatch));
+ }, dispatch, ownProps));
}
diff --git a/packages/unleash-frontend-next/src/component/strategies/add-container.js b/packages/unleash-frontend-next/src/component/strategies/add-container.js
index d3f21d96be..b9228ff1cf 100644
--- a/packages/unleash-frontend-next/src/component/strategies/add-container.js
+++ b/packages/unleash-frontend-next/src/component/strategies/add-container.js
@@ -7,7 +7,7 @@ import AddStrategy, { PARAM_PREFIX } from './add-strategy';
const ID = 'add-strategy';
-const actions = createActions(ID, (methods, dispatch) => {
+const prepare = (methods, dispatch) => {
methods.onSubmit = (input) => (
(e) => {
e.preventDefault();
@@ -35,6 +35,11 @@ const actions = createActions(ID, (methods, dispatch) => {
return methods;
+};
+
+const actions = createActions({
+ id: ID,
+ prepare,
});
-export default connect(createMapper(ID), actions)(AddStrategy);
+export default connect(createMapper({ id: ID }), actions)(AddStrategy);
diff --git a/packages/unleash-frontend-next/src/store/input-actions.js b/packages/unleash-frontend-next/src/store/input-actions.js
index d8c2f96a99..2f055c7b96 100644
--- a/packages/unleash-frontend-next/src/store/input-actions.js
+++ b/packages/unleash-frontend-next/src/store/input-actions.js
@@ -1,11 +1,17 @@
export const actions = {
SET_VALUE: 'SET_VALUE',
INCREMENT_VALUE: 'INCREMENT_VALUE',
+ LIST_PUSH: 'LIST_PUSH',
+ LIST_POP: 'LIST_POP',
CLEAR: 'CLEAR',
+ INIT: 'INIT',
};
+export const createInit = ({ id, value }) => ({ type: actions.INIT, id, value });
export const createInc = ({ id, key }) => ({ type: actions.INCREMENT_VALUE, id, key });
export const createSet = ({ id, key, value }) => ({ type: actions.SET_VALUE, id, key, value });
+export const createPush = ({ id, key, value }) => ({ type: actions.LIST_PUSH, id, key, value });
+export const createPop = ({ id, key, value }) => ({ type: actions.LIST_POP, id, key, value });
export const createClear = ({ id }) => ({ type: actions.CLEAR, id });
export default actions;
diff --git a/packages/unleash-frontend-next/src/store/input-store.js b/packages/unleash-frontend-next/src/store/input-store.js
index 109417a08d..93119dab24 100644
--- a/packages/unleash-frontend-next/src/store/input-store.js
+++ b/packages/unleash-frontend-next/src/store/input-store.js
@@ -1,41 +1,68 @@
-import { Map as $Map } from 'immutable';
+import { Map as $Map, List, fromJS } from 'immutable';
import actions from './input-actions';
function getInitState () {
return new $Map();
}
+function init (state, { id, value }) {
+ state = assertId(state, id);
+ return state.setIn(id, fromJS(value));
+}
+
function assertId (state, id) {
- if (!state.has(id)) {
- return state.set(id, new $Map({ inputId: id }));
+ if (!state.hasIn(id)) {
+ return state.setIn(id, new $Map({ inputId: id }));
+ }
+ return state;
+}
+
+function assertList (state, id, key) {
+ if (!state.getIn(id).has(key)) {
+ return state.setIn(id.concat([key]), new List());
}
return state;
}
function setKeyValue (state, { id, key, value }) {
state = assertId(state, id);
- return state.setIn([id, key], value);
+ return state.setIn(id.concat([key]), value);
}
function increment (state, { id, key }) {
state = assertId(state, id);
- return state.updateIn([id, key], (value = 0) => value + 1);
+ return state.updateIn(id.concat([key]), (value = 0) => value + 1);
}
function clear (state, { id }) {
- if (state.has(id)) {
- return state.remove(id);
+ if (state.hasIn(id)) {
+ return state.removeIn(id);
}
return state;
}
-const inputState = (state = getInitState(), action) => {
+function addToList (state, { id, key, value }) {
+ state = assertId(state, id);
+ state = assertList(state, id, key);
+ return state.updateIn(id.concat([key]), (list) => list.push(value));
+}
+
+function removeFromList (state, { id, key, value }) {
+ state = assertId(state, id);
+ state = assertList(state, id, key);
+
+ return state.updateIn(id.concat([key]), (list) => list.remove(list.indexOf(value)));
+}
+
+const inputState = (state = getInitState(), action) => {
if (!action.id) {
return state;
}
switch (action.type) {
+ case actions.INIT:
+ return init(state, action);
case actions.SET_VALUE:
if (actions.key != null && actions.value != null) {
throw new Error('Missing required key / value');
@@ -43,6 +70,10 @@ const inputState = (state = getInitState(), action) => {
return setKeyValue(state, action);
case actions.INCREMENT_VALUE:
return increment(state, action);
+ case actions.LIST_PUSH:
+ return addToList(state, action);
+ case actions.LIST_POP:
+ return removeFromList(state, action);
case actions.CLEAR:
return clear(state, action);
default: