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

View File

@ -18,6 +18,7 @@ const prepare = (methods, dispatch) => {
);
methods.onCancel = () => {
debugger;
window.history.back();
};
@ -25,6 +26,10 @@ const prepare = (methods, dispatch) => {
methods.pushToList('strategies', v);
};
methods.updateStrategy = (v, n) => {
methods.updateInList('strategies', v, n);
};
methods.removeStrategy = (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.updateStrategy = (v, n) => {
methods.updateInList('strategies', v, n);
};
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 { Button, Input } from 'react-toolbox';
import { Button } from 'react-toolbox';
class AddStrategy extends React.Component {
constructor (props) {
@ -17,11 +17,6 @@ class AddStrategy extends React.Component {
};
}
componentWillMount () {
// TODO: move somewhere appropriate?
this.props.fetchStrategies();
}
componentWillReceiveProps (nextProps) {
// this will fix async strategies list loading after mounted
if (!this.state.selectedStrategy && nextProps.strategies.length > 0) {
@ -65,9 +60,7 @@ class AddStrategy extends React.Component {
<select value={selectedStrategy.name} onChange={this.handleChange}>
{strategies}
</select>
<Button icon="add" accent flat label="add strategy" onClick={this.addStrategy} />
<p><strong>Description:</strong> {selectedStrategy.description}</p>
</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 AddStrategy from './add-strategy';
import StrategiesSection from './strategies-section';
import { fetchStrategies } from '../../store/strategy-actions';
export default connect((state) => ({
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,
createPop,
createPush,
createUp,
createInit,
} from '../store/input-actions';
@ -56,6 +57,10 @@ export function createActions ({ id, prepare = (v) => v }) {
dispatch(createPop({ id: getId(id, ownProps), key, value }));
},
updateInList (key, value, newValue) {
dispatch(createUp({ id: getId(id, ownProps), key, value, newValue }));
},
incValue (key) {
dispatch(createInc({ id: getId(id, ownProps), key }));
},

View File

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

View File

@ -3,6 +3,7 @@ export const actions = {
INCREMENT_VALUE: 'INCREMENT_VALUE',
LIST_PUSH: 'LIST_PUSH',
LIST_POP: 'LIST_POP',
LIST_UP: 'LIST_UP',
CLEAR: 'CLEAR',
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 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 createUp = ({ id, key, value, newValue }) => ({ type: actions.LIST_UP, id, key, value, newValue });
export const createClear = ({ id }) => ({ type: actions.CLEAR, id });
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));
}
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 }) {
state = assertId(state, id);
state = assertList(state, id, key);
@ -74,6 +81,8 @@ const inputState = (state = getInitState(), action) => {
return addToList(state, action);
case actions.LIST_POP:
return removeFromList(state, action);
case actions.LIST_UP:
return updateInList(state, action);
case actions.CLEAR:
return clear(state, action);
default: