1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-28 00:17:12 +01:00

Update input field for feature toggles

This commit is contained in:
ivaosthu 2016-10-25 22:04:43 +02:00
parent 9b7b487fdc
commit c867d602e8
16 changed files with 195 additions and 243 deletions

View File

@ -2,8 +2,7 @@ import React, { Component, PropTypes } from 'react';
import Input from 'react-toolbox/lib/input'; import Input from 'react-toolbox/lib/input';
import Button from 'react-toolbox/lib/button'; import Button from 'react-toolbox/lib/button';
import Switch from 'react-toolbox/lib/switch'; import Switch from 'react-toolbox/lib/switch';
import SelectStrategies from './strategies-for-toggle-container'; import StrategiesSection from './strategies-section-container';
import SelectedStrategies from './selected-strategies';
class AddFeatureToggleComponent extends Component { class AddFeatureToggleComponent extends Component {
@ -20,6 +19,7 @@ class AddFeatureToggleComponent extends Component {
setValue, setValue,
addStrategy, addStrategy,
removeStrategy, removeStrategy,
updateStrategy,
onSubmit, onSubmit,
onCancel, onCancel,
editmode = false, editmode = false,
@ -59,23 +59,11 @@ class AddFeatureToggleComponent extends Component {
<br /> <br />
</section> </section>
<section> <StrategiesSection
<div>
<h5 style={{
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
paddingBottom: '5px',
marginBottom: '10px',
}}>Strategies:</h5>
</div>
<SelectedStrategies
configuredStrategies={configuredStrategies} configuredStrategies={configuredStrategies}
addStrategy={addStrategy}
updateStrategy={updateStrategy}
removeStrategy={removeStrategy} /> removeStrategy={removeStrategy} />
</section>
<section>
<SelectStrategies addStrategy={addStrategy} />
</section>
<br /> <br />
@ -89,13 +77,13 @@ class AddFeatureToggleComponent extends Component {
}; };
AddFeatureToggleComponent.propTypes = { AddFeatureToggleComponent.propTypes = {
strategies: PropTypes.array.required,
input: PropTypes.object, input: PropTypes.object,
setValue: PropTypes.func.required, setValue: PropTypes.func.isRequired,
addStrategy: PropTypes.func.required, addStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.required, removeStrategy: PropTypes.func.isRequired,
onSubmit: PropTypes.func.required, updateStrategy: PropTypes.func.isRequired,
onCancel: PropTypes.func.required, onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
editmode: PropTypes.bool, editmode: PropTypes.bool,
}; };

View File

@ -18,6 +18,7 @@ const prepare = (methods, dispatch) => {
); );
methods.onCancel = () => { methods.onCancel = () => {
debugger;
window.history.back(); window.history.back();
}; };
@ -25,6 +26,10 @@ const prepare = (methods, dispatch) => {
methods.pushToList('strategies', v); methods.pushToList('strategies', v);
}; };
methods.updateStrategy = (v, n) => {
methods.updateInList('strategies', v, n);
};
methods.removeStrategy = (v) => { methods.removeStrategy = (v) => {
methods.removeFromList('strategies', v); methods.removeFromList('strategies', v);
}; };

View File

@ -0,0 +1,40 @@
import React, { PropTypes } from 'react';
import ConfigureStrategy from './configure-strategy';
class ConfigureStrategies extends React.Component {
static propTypes () {
return {
strategies: PropTypes.array.isRequired,
configuredStrategies: PropTypes.array.isRequired,
updateStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.isRequired,
};
}
render () {
const {
strategies,
configuredStrategies,
} = this.props;
if (!configuredStrategies || configuredStrategies.length === 0) {
return <i>No strategies added</i>;
}
const blocks = configuredStrategies.map((strat, i) => (
<ConfigureStrategy
key={`${strat.name}-${i}`}
strategy={strat}
removeStrategy={this.props.removeStrategy}
updateStrategy={this.props.updateStrategy}
strategyDefinition={strategies.find(s => s.name === strat.name)} />
));
return (
<div>
{blocks}
</div>
);
}
}
export default ConfigureStrategies;

View File

@ -0,0 +1,65 @@
import React, { PropTypes } from 'react';
import { Button, Input } from 'react-toolbox';
class ConfigureStrategies extends React.Component {
static propTypes () {
return {
strategy: PropTypes.object.isRequired,
strategyDefinition: PropTypes.object.isRequired,
updateStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.isRequired,
};
}
updateStrategy = (evt) => {
evt.preventDefault();
this.props.updateStrategy({
name: this.state.selectedStrategy.name,
parameters: this.state.parameters,
});
};
handleConfigChange = (key, value) => {
const parameters = {};
parameters[key] = value;
const updatedStrategy = Object.assign({}, this.props.strategy, { parameters });
this.props.updateStrategy(this.props.strategy, updatedStrategy);
};
handleRemove = (evt) => {
evt.preventDefault();
this.props.removeStrategy(this.props.strategy);
}
renderInputFields (strategyDefinition) {
if (strategyDefinition.parametersTemplate) {
return Object.keys(strategyDefinition.parametersTemplate).map(field => (
<Input
type="text"
key={field}
name={field}
label={field}
onChange={this.handleConfigChange.bind(this, field)}
value={this.props.strategy.parameters[field]}
/>
));
}
}
render () {
const inputFields = this.renderInputFields(this.props.strategyDefinition);
return (
<div>
<strong>{this.props.strategy.name}</strong>
<Button title="Remove Strategy" onClick={this.handleRemove} icon="remove" floating accent mini />
<p><i>{this.props.strategyDefinition.description}</i></p>
{inputFields}
</div>
);
}
}
export default ConfigureStrategies;

View File

@ -51,6 +51,10 @@ const prepare = (methods, dispatch) => {
methods.removeFromList('strategies', v); methods.removeFromList('strategies', v);
}; };
methods.updateStrategy = (v, n) => {
methods.updateInList('strategies', v, n);
};
return methods; return methods;
}; };

View File

@ -1,8 +0,0 @@
import { connect } from 'react-redux';
import AddStrategy from './add-strategy';
import { fetchStrategies } from '../../store/strategy-actions';
export default connect((state) => ({
strategies: state.strategies.get('list').toArray(),
}), { fetchStrategies })(AddStrategy);

View File

@ -1,101 +0,0 @@
import React, { PropTypes } from 'react';
import Input from 'react-toolbox/lib/input';
import Button from 'react-toolbox/lib/button';
class SelectStrategies extends React.Component {
constructor (props) {
super(props);
this.state = {
selectedStrategy: props.strategies[0],
parameters: {},
};
}
static propTypes () {
return {
strategies: PropTypes.array.isRequired,
cancelConfig: PropTypes.func.isRequired,
addStrategy: PropTypes.func.isRequired,
};
}
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);
this.setState({ selectedStrategy, parameters: {} });
}
addStrategy = (evt) => {
evt.preventDefault();
this.props.addStrategy({
name: this.state.selectedStrategy.name,
parameters: this.state.parameters,
});
};
handleConfigChange = (key, value) => {
const parameters = this.state.parameters;
parameters[key] = value;
this.setState({ parameters });
};
renderInputFields (selectedStrategy) {
if (selectedStrategy.parametersTemplate) {
return Object.keys(selectedStrategy.parametersTemplate).map(field => (
<Input
type="text"
key={field}
name={field}
label={field}
onChange={this.handleConfigChange.bind(this, field)}
value={this.state.parameters[field]}
/>
));
}
}
render () {
const strategies = this.props.strategies.map(s => (
<option key={s.name} value={s.name}>{s.name}</option>
));
const style = {
backgroundColor: '#ECE',
padding: '10px',
};
const selectedStrategy = this.state.selectedStrategy || this.props.strategies[0];
if (!selectedStrategy) {
return <div>Strategies loading...</div>;
}
return (
<div style={style}>
<select value={selectedStrategy.name} onChange={this.handleChange}>
{strategies}
</select>
<p><strong>Description:</strong> {selectedStrategy.description}</p>
{this.renderInputFields(selectedStrategy)}
<Button icon="add" accent label="add strategy" onClick={this.addStrategy} />
<Button label="cancel" onClick={this.props.cancelConfig} />
</div>
);
}
}
export default SelectStrategies;

View File

@ -1,47 +0,0 @@
import React, { PropTypes } from 'react';
import Chip from 'react-toolbox/lib/chip';
import Avatar from 'react-toolbox/lib/avatar';
class SelectedStrategies extends React.Component {
static propTypes () {
return {
configuredStrategies: PropTypes.array.isRequired,
removeStrategy: PropTypes.func.isRequired,
};
}
renderName (strategy) {
const parameters = strategy.parameters || {};
const keys = Object.keys(parameters);
if (keys.length === 0) {
return <span>{strategy.name}</span>;
}
const params = keys
.map(param => `${param}="${strategy.parameters[param]}"`)
.join('; ');
return <span>{strategy.name} ({params})</span>;
}
render () {
const removeStrategy = this.props.removeStrategy;
const configuredStrategies = this.props.configuredStrategies.map((s, index) => (
<Chip
style={{ marginRight: '10px', marginBottom: '10px' }}
key={`${index}-${s.name}`}
deletable
onDeleteClick={() => removeStrategy(s)}
>
<Avatar icon="edit" />
{this.renderName(s)}
</Chip>
));
return (
<div>
{configuredStrategies.length > 0 ? configuredStrategies : <p>No activation strategies added</p>}
</div>
);
}
}
export default SelectedStrategies;

View File

@ -1,5 +1,5 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { Button, Input } from 'react-toolbox'; import { Button } from 'react-toolbox';
class AddStrategy extends React.Component { class AddStrategy extends React.Component {
constructor (props) { constructor (props) {
@ -17,11 +17,6 @@ class AddStrategy extends React.Component {
}; };
} }
componentWillMount () {
// TODO: move somewhere appropriate?
this.props.fetchStrategies();
}
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
// this will fix async strategies list loading after mounted // this will fix async strategies list loading after mounted
if (!this.state.selectedStrategy && nextProps.strategies.length > 0) { if (!this.state.selectedStrategy && nextProps.strategies.length > 0) {
@ -65,9 +60,7 @@ class AddStrategy extends React.Component {
<select value={selectedStrategy.name} onChange={this.handleChange}> <select value={selectedStrategy.name} onChange={this.handleChange}>
{strategies} {strategies}
</select> </select>
<Button icon="add" accent flat label="add strategy" onClick={this.addStrategy} /> <Button icon="add" accent flat label="add strategy" onClick={this.addStrategy} />
<p><strong>Description:</strong> {selectedStrategy.description}</p> <p><strong>Description:</strong> {selectedStrategy.description}</p>
</div> </div>
); );

View File

@ -1,50 +0,0 @@
import React, { PropTypes } from 'react';
import Button from 'react-toolbox/lib/button';
class AddStrategiesToToggle extends React.Component {
constructor (props) {
super(props);
this.state = {
selectedStrategy: undefined,
};
}
static propTypes () {
return {
addStrategy: PropTypes.func.isRequired,
strategies: PropTypes.array.isRequired,
fetchStrategies: PropTypes.func.isRequired,
};
}
componentWillMount () {
// TODO: move somewhere appropriate?
this.props.fetchStrategies();
}
addStrategy = (strategy) => {
this.setState({ selectedStrategy: undefined });
this.props.addStrategy(strategy);
}
render () {
const selectedStrategy = this.state.selectedStrategy || this.props.strategies[0];
const strategies = this.props.strategies.map(s => (
<option key={s.name} value={s.name}>{s.name}</option>
));
return (
<div>
<select value={selectedStrategy.name} onChange={this.handleChange}>
{strategies}
</select>
<Button icon="add" accent onClick={this.showConfigure}>Add strategy</Button>
</div>
);
}
}
export default AddStrategiesToToggle;

View File

@ -1,8 +1,8 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import AddStrategy from './add-strategy'; import StrategiesSection from './strategies-section';
import { fetchStrategies } from '../../store/strategy-actions'; import { fetchStrategies } from '../../store/strategy-actions';
export default connect((state) => ({ export default connect((state) => ({
strategies: state.strategies.get('list').toArray(), strategies: state.strategies.get('list').toArray(),
}), { fetchStrategies })(AddStrategy); }), { fetchStrategies })(StrategiesSection);

View File

@ -0,0 +1,44 @@
import React, { PropTypes } from 'react';
import ConfigureStrategies from './configure-strategies';
import AddStrategy from './strategies-add';
const headerStyle = {
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
paddingBottom: '5px',
marginBottom: '10px',
};
class StrategiesSection extends React.Component {
static propTypes () {
return {
strategies: PropTypes.array.isRequired,
addStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.isRequired,
updateStrategy: PropTypes.func.isRequired,
fetchStrategies: PropTypes.func.isRequired,
};
}
componentWillMount () {
this.props.fetchStrategies();
}
render () {
if (!this.props.strategies || this.props.strategies.length === 0) {
return <i>Loding available strategies</i>;
}
return (
<div>
<div>
<h5 style={headerStyle}>Strategies:</h5>
<ConfigureStrategies {...this.props} />
<AddStrategy {...this.props} />
</div>
</div>
);
}
}
export default StrategiesSection;

View File

@ -4,6 +4,7 @@ import {
createSet, createSet,
createPop, createPop,
createPush, createPush,
createUp,
createInit, createInit,
} from '../store/input-actions'; } from '../store/input-actions';
@ -56,6 +57,10 @@ export function createActions ({ id, prepare = (v) => v }) {
dispatch(createPop({ id: getId(id, ownProps), key, value })); dispatch(createPop({ id: getId(id, ownProps), key, value }));
}, },
updateInList (key, value, newValue) {
dispatch(createUp({ id: getId(id, ownProps), key, value, newValue }));
},
incValue (key) { incValue (key) {
dispatch(createInc({ id: getId(id, ownProps), key })); dispatch(createInc({ id: getId(id, ownProps), key }));
}, },

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Router, Route, IndexRedirect, hashHistory } from 'react-router'; import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
@ -16,6 +17,8 @@ import CreateStrategies from './page/strategies/create';
import HistoryPage from './page/history'; import HistoryPage from './page/history';
import Archive from './page/archive'; import Archive from './page/archive';
const appHistory = useRouterHistory(createHashHistory)({ queryKey: false });
const unleashStore = createStore( const unleashStore = createStore(
store, store,
applyMiddleware( applyMiddleware(
@ -25,7 +28,7 @@ const unleashStore = createStore(
ReactDOM.render( ReactDOM.render(
<Provider store={unleashStore}> <Provider store={unleashStore}>
<Router history={hashHistory}> <Router history={appHistory}>
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRedirect to="/features" /> <IndexRedirect to="/features" />
<Route path="/features" component={Features} /> <Route path="/features" component={Features} />

View File

@ -3,6 +3,7 @@ export const actions = {
INCREMENT_VALUE: 'INCREMENT_VALUE', INCREMENT_VALUE: 'INCREMENT_VALUE',
LIST_PUSH: 'LIST_PUSH', LIST_PUSH: 'LIST_PUSH',
LIST_POP: 'LIST_POP', LIST_POP: 'LIST_POP',
LIST_UP: 'LIST_UP',
CLEAR: 'CLEAR', CLEAR: 'CLEAR',
INIT: 'INIT', INIT: 'INIT',
}; };
@ -12,6 +13,7 @@ export const createInc = ({ id, key }) => ({ type: actions.INCREMENT_VALUE, id,
export const createSet = ({ id, key, value }) => ({ type: actions.SET_VALUE, id, key, value }); 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 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 createPop = ({ id, key, value }) => ({ type: actions.LIST_POP, id, key, value });
export const createUp = ({ id, key, value, newValue }) => ({ type: actions.LIST_UP, id, key, value, newValue });
export const createClear = ({ id }) => ({ type: actions.CLEAR, id }); export const createClear = ({ id }) => ({ type: actions.CLEAR, id });
export default actions; export default actions;

View File

@ -48,6 +48,13 @@ function addToList (state, { id, key, value }) {
return state.updateIn(id.concat([key]), (list) => list.push(value)); return state.updateIn(id.concat([key]), (list) => list.push(value));
} }
function updateInList (state, { id, key, value, newValue }) {
state = assertId(state, id);
state = assertList(state, id, key);
return state.updateIn(id.concat([key]), (list) => list.set(list.indexOf(value), newValue));
}
function removeFromList (state, { id, key, value }) { function removeFromList (state, { id, key, value }) {
state = assertId(state, id); state = assertId(state, id);
state = assertList(state, id, key); state = assertList(state, id, key);
@ -74,6 +81,8 @@ const inputState = (state = getInitState(), action) => {
return addToList(state, action); return addToList(state, action);
case actions.LIST_POP: case actions.LIST_POP:
return removeFromList(state, action); return removeFromList(state, action);
case actions.LIST_UP:
return updateInList(state, action);
case actions.CLEAR: case actions.CLEAR:
return clear(state, action); return clear(state, action);
default: default: