1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

fix: remove use of input stores

This commit is contained in:
Ivar Conradi Østhus 2021-01-06 21:41:56 +01:00
parent a5bd890e12
commit 46843aecf1
12 changed files with 165 additions and 398 deletions

View File

@ -75,17 +75,17 @@
"optimize-css-assets-webpack-plugin": "^5.0.0",
"prettier": "^1.18.2",
"prop-types": "^15.6.2",
"react": "^16.13.1",
"react": "^16.14.0",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^16.13.1",
"react-dom": "^16.14.0",
"react-mdl": "^2.1.0",
"react-modal": "^3.1.13",
"react-redux": "^7.2.0",
"react-router-dom": "^5.1.2",
"react-select": "^3.1.0",
"react-timeago": "^4.4.0",
"react-test-renderer": "^16.13.1",
"react-test-renderer": "^16.14.0",
"redux": "^4.0.5",
"redux-devtools": "^3.5.0",
"redux-mock-store": "^1.5.4",

View File

@ -1,81 +0,0 @@
import {
createInc,
createClear,
createSet,
createPop,
createPush,
createUp,
createInit,
createMove,
} from '../store/input-actions';
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;
let initCallRequired = false;
const scope = getId(id, ownProps);
if (state.input.hasIn(scope)) {
input = state.input.getIn(scope).toJS();
} else {
initCallRequired = true;
input = getDefault ? getDefault(state, ownProps) : {};
}
return prepare(
{
initCallRequired,
input,
},
state,
ownProps
);
};
}
export function createActions({ id, prepare = v => v }) {
return (dispatch, ownProps) =>
prepare(
{
clear() {
dispatch(createClear({ id: getId(id, ownProps) }));
},
init(value) {
dispatch(createInit({ id: getId(id, ownProps), value }));
},
setValue(key, value) {
dispatch(createSet({ id: getId(id, ownProps), key, value }));
},
pushToList(key, value) {
dispatch(createPush({ id: getId(id, ownProps), key, value }));
},
removeFromList(key, index) {
dispatch(createPop({ id: getId(id, ownProps), key, index }));
},
moveItem(key, index, toIndex) {
dispatch(createMove({ id: getId(id, ownProps), key, index, toIndex }));
},
updateInList(key, index, newValue, merge = false) {
dispatch(createUp({ id: getId(id, ownProps), key, index, newValue, merge }));
},
incValue(key) {
dispatch(createInc({ id: getId(id, ownProps), key }));
},
},
dispatch,
ownProps
);
}

View File

@ -1,62 +0,0 @@
import { connect } from 'react-redux';
import { createMapper, createActions } from './../input-helpers';
import { createStrategy } from './../../store/strategy/actions';
import AddStrategy from './add-strategy';
const ID = 'add-strategy';
const prepare = (methods, dispatch) => {
methods.onSubmit = input => e => {
e.preventDefault();
// clean
const parameters = (input.parameters || [])
.filter(name => !!name)
.map(({ name, type = 'string', description = '', required = false }) => ({
name,
type,
description,
required,
}));
createStrategy({
name: input.name,
description: input.description,
parameters,
})(dispatch)
.then(() => methods.clear())
// somewhat quickfix / hacky to go back..
.then(() => window.history.back());
};
methods.onCancel = e => {
e.preventDefault();
methods.clear();
// somewhat quickfix / hacky to go back..
window.history.back();
};
return methods;
};
const actions = createActions({
id: ID,
prepare,
});
export default connect(
createMapper({
id: ID,
getDefault() {
let name;
try {
[, name] = document.location.hash.match(/name=([a-z0-9-_.]+)/i);
} catch (e) {
// hide error
}
return { name };
},
}),
actions
)(AddStrategy);

View File

@ -1,60 +0,0 @@
import { connect } from 'react-redux';
import { createMapper, createActions } from '../input-helpers';
import { updateStrategy } from '../../store/strategy/actions';
import AddStrategy from './add-strategy';
const ID = 'edit-strategy';
function getId(props) {
return [ID, props.strategy.name];
}
// TODO: need to scope to the active strategy
// best is to emulate the "input-storage"?
const mapStateToProps = createMapper({
id: getId,
getDefault: (state, ownProps) => ownProps.strategy,
prepare: props => {
props.editmode = true;
return props;
},
});
const prepare = (methods, dispatch, ownProps) => {
methods.onSubmit = input => e => {
e.preventDefault();
// clean
const parameters = (input.parameters || [])
.filter(name => !!name)
.map(({ name, type = 'string', description = '', required = false }) => ({
name,
type,
description,
required,
}));
updateStrategy({
name: input.name,
description: input.description,
parameters,
})(dispatch)
.then(() => methods.clear())
.then(() => ownProps.history.push(`/strategies/view/${input.name}`));
};
methods.onCancel = e => {
e.preventDefault();
methods.clear();
ownProps.history.push(`/strategies/view/${ownProps.strategy.name}`);
};
return methods;
};
const actions = createActions({
id: getId,
prepare,
});
export default connect(mapStateToProps, actions)(AddStrategy);

View File

@ -0,0 +1,120 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStrategy, updateStrategy } from '../../store/strategy/actions';
import AddStrategy from './from-strategy';
import { loadNameFromHash } from '../common/util';
class WrapperComponent extends Component {
constructor(props) {
super(props);
this.state = {
strategy: this.props.strategy,
errors: {},
dirty: false,
};
}
appParameter = () => {
const { strategy } = this.state;
strategy.parameters = [...strategy.parameters, {}];
this.setState({ strategy, dirty: true });
};
updateParameter = (index, updated) => {
const { strategy } = this.state;
// 1. Make a shallow copy of the items
let parameters = [...strategy.parameters];
// 2. Make a shallow copy of the item you want to mutate
let item = { ...parameters[index] };
// 3. Replace the property you're intested in
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
parameters[index] = Object.assign({}, item, updated);
// 5. Set the state to our new copy
strategy.parameters = parameters;
this.setState({ strategy });
};
setValue = (field, value) => {
const { strategy } = this.state;
strategy[field] = value;
this.setState({ strategy, dirty: true });
};
onSubmit = async evt => {
evt.preventDefault();
const { createStrategy, updateStrategy, history, editMode } = this.props;
const { strategy } = this.state;
const parameters = (strategy.parameters || [])
.filter(({ name }) => !!name)
.map(({ name, type = 'string', description = '', required = false }) => ({
name,
type,
description,
required,
}));
strategy.parameters = parameters;
if (editMode) {
await updateStrategy(strategy);
history.push(`/strategies/view/${strategy.name}`);
} else {
await createStrategy(strategy);
history.push(`/strategies`);
}
};
onCancel = evt => {
evt.preventDefault();
const { history, editMode } = this.props;
const { strategy } = this.state;
if (editMode) {
history.push(`/strategies/view/${strategy.name}`);
} else {
history.push('/strategies');
}
};
render() {
return (
<AddStrategy
onSubmit={this.onSubmit}
onCancel={this.onCancel}
setValue={this.setValue}
updateParameter={this.updateParameter}
appParameter={this.appParameter}
input={this.state.strategy}
errors={this.state.errors}
editMode={this.props.editMode}
/>
);
}
}
WrapperComponent.propTypes = {
history: PropTypes.object.isRequired,
createStrategy: PropTypes.func.isRequired,
updateStrategy: PropTypes.func.isRequired,
strategy: PropTypes.object,
editMode: PropTypes.bool,
};
const mapDispatchToProps = { createStrategy, updateStrategy };
const mapStateToProps = (state, props) => {
const { strategy, editMode } = props;
return {
strategy: strategy ? strategy : { name: loadNameFromHash(), description: '', parameters: [] },
editMode,
};
};
const FormAddContainer = connect(mapStateToProps, mapDispatchToProps)(WrapperComponent);
export default FormAddContainer;

View File

@ -3,14 +3,7 @@ import PropTypes from 'prop-types';
import { Textfield, IconButton, Menu, MenuItem, Checkbox, CardTitle, Card, CardActions } from 'react-mdl';
import { styles as commonStyles, FormButtons } from '../common';
const trim = value => {
if (value && value.trim) {
return value.trim();
} else {
return value;
}
};
import { trim } from '../common/util';
function gerArrayWithEntries(num) {
return Array.from(Array(num));
@ -63,7 +56,6 @@ const Parameter = ({ set, input = {}, index }) => (
checked={!!input.required}
onChange={() => set({ required: !input.required })}
ripple
defaultChecked
/>
</div>
);
@ -88,17 +80,17 @@ const CreateHeader = () => (
</div>
);
const Parameters = ({ input = [], count = 0, updateInList }) => (
const Parameters = ({ input = [], count = 0, updateParameter }) => (
<div>
{gerArrayWithEntries(count).map((v, i) => (
<Parameter key={i} set={v => updateInList('parameters', i, v, true)} index={i} input={input[i]} />
<Parameter key={i} set={v => updateParameter(i, v, true)} index={i} input={input[i]} />
))}
</div>
);
Parameters.propTypes = {
input: PropTypes.array,
updateInList: PropTypes.func.isRequired,
updateParameter: PropTypes.func.isRequired,
count: PropTypes.number,
};
@ -106,43 +98,30 @@ class AddStrategy extends Component {
static propTypes = {
input: PropTypes.object,
setValue: PropTypes.func,
updateInList: PropTypes.func,
incValue: PropTypes.func,
appParameter: PropTypes.func,
updateParameter: PropTypes.func,
clear: PropTypes.func,
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
editmode: PropTypes.bool,
initCallRequired: PropTypes.bool,
init: PropTypes.func,
editMode: PropTypes.bool,
};
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
// TODO unwind this stuff
if (this.props.initCallRequired === true) {
this.props.init(this.props.input);
if (this.props.input.parameters) {
this.props.setValue('_params', this.props.input.parameters.length);
}
}
}
render() {
const { input, setValue, updateInList, incValue, onCancel, editmode = false, onSubmit } = this.props;
const { input, setValue, appParameter, onCancel, editMode = false, onSubmit, updateParameter } = this.props;
return (
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible', paddingBottom: '10px' }}>
<CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>
{editmode ? <EditHeader /> : <CreateHeader />}
{editMode ? <EditHeader /> : <CreateHeader />}
</CardTitle>
<form onSubmit={onSubmit(input)}>
<form onSubmit={onSubmit}>
<section style={{ padding: '16px' }}>
<Textfield
label="Strategy name"
floatingLabel
name="name"
required
disabled={editmode}
placeholder=""
disabled={editMode}
onChange={({ target }) => setValue('name', trim(target.value))}
value={input.name}
/>
@ -153,23 +132,28 @@ class AddStrategy extends Component {
rows={1}
label="Description"
name="description"
placeholder=""
onChange={({ target }) => setValue('description', target.value)}
value={input.description}
/>
<Parameters input={input.parameters} count={input._params} updateInList={updateInList} />
<Parameters
input={input.parameters}
count={input.parameters.length}
updateParameter={updateParameter}
/>
<IconButton
raised
name="add"
title="Add parameter"
onClick={e => {
e.preventDefault();
incValue('_params');
appParameter();
}}
/>{' '}
&nbsp;Add parameter
</section>
<CardActions>
<FormButtons submitText={editmode ? 'Update' : 'Create'} onCancel={onCancel} />
<FormButtons submitText={editMode ? 'Update' : 'Create'} onCancel={onCancel} />
</CardActions>
</form>
</Card>

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Tabs, Tab, ProgressBar, Grid, Cell } from 'react-mdl';
import ShowStrategy from './show-strategy-component';
import EditStrategy from './edit-container';
import EditStrategy from './form-container';
import { HeaderTitle } from '../common';
import { UPDATE_STRATEGY } from '../../permissions';
@ -39,7 +39,7 @@ export default class StrategyDetails extends Component {
getTabContent(activeTabId) {
if (activeTabId === TABS.edit) {
return <EditStrategy strategy={this.props.strategy} history={this.props.history} />;
return <EditStrategy strategy={this.props.strategy} history={this.props.history} editMode />;
} else {
return (
<ShowStrategy

View File

@ -1,5 +1,5 @@
import React from 'react';
import AddStrategies from '../../component/strategies/add-container';
import AddStrategies from '../../component/strategies/form-container';
import PropTypes from 'prop-types';
const render = ({ history }) => <AddStrategies history={history} />;

View File

@ -3,7 +3,6 @@ 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';
import history from './history-store'; // eslint-disable-line
import archive from './archive-store';
import error from './error-store';
@ -20,7 +19,6 @@ const unleashStore = combineReducers({
featureTypes,
featureMetrics,
strategies,
input,
history,
archive,
error,

View File

@ -1,28 +0,0 @@
export const actions = {
SET_VALUE: 'SET_VALUE',
INCREMENT_VALUE: 'INCREMENT_VALUE',
LIST_PUSH: 'LIST_PUSH',
LIST_POP: 'LIST_POP',
LIST_UP: 'LIST_UP',
CLEAR: 'CLEAR',
INIT: 'INIT',
MOVE: 'MOVE',
};
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, index }) => ({ type: actions.LIST_POP, id, key, index });
export const createMove = ({ id, key, index, toIndex }) => ({ type: actions.MOVE, id, key, index, toIndex });
export const createUp = ({ id, key, index, newValue, merge }) => ({
type: actions.LIST_UP,
id,
key,
index,
newValue,
merge,
});
export const createClear = ({ id }) => ({ type: actions.CLEAR, id });
export default actions;

View File

@ -1,114 +0,0 @@
import { Map as $Map, List, fromJS } from 'immutable';
import actions from './input-actions';
import { USER_LOGOUT, USER_LOGIN } from './user/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.hasIn(id)) {
return state.setIn(id, new $Map({ inputId: id }));
}
return state;
}
function assertList(state, id, key) {
if (!state.getIn(id).has(key) || state.getIn(id).get(key) == null) {
return state.setIn(id.concat([key]), new List());
}
return state;
}
function setKeyValue(state, { id, key, value }) {
state = assertId(state, id);
return state.setIn(id.concat([key]), value);
}
function increment(state, { id, key }) {
state = assertId(state, id);
return state.updateIn(id.concat([key]), (value = 0) => value + 1);
}
function clear(state, { id }) {
if (state.hasIn(id)) {
return state.removeIn(id);
}
return state;
}
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 updateInList(state, { id, key, index, newValue, merge }) {
state = assertId(state, id);
state = assertList(state, id, key);
return state.updateIn(id.concat([key]), list => {
if (merge && list.has(index)) {
newValue = list.get(index).merge(new $Map(newValue));
} else if (typeof newValue !== 'string') {
newValue = fromJS(newValue);
}
return list.set(index, newValue);
});
}
function removeFromList(state, { id, key, index }) {
state = assertId(state, id);
state = assertList(state, id, key);
return state.updateIn(id.concat([key]), list => list.remove(index));
}
function move(state, { id, key, index, toIndex }) {
return state.updateIn(id.concat([key]), list => {
const olditem = list.get(index);
return list.delete(index).insert(toIndex, olditem);
});
}
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');
}
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.MOVE:
return move(state, action);
case actions.LIST_UP:
return updateInList(state, action);
case actions.CLEAR:
return clear(state, action);
case USER_LOGOUT:
case USER_LOGIN:
return getInitState();
default:
// console.log('TYPE', action.type, action);
return state;
}
};
export default inputState;

View File

@ -7843,10 +7843,10 @@ react-dnd@^11.1.3:
dnd-core "^11.1.3"
hoist-non-react-statics "^3.3.0"
react-dom@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
react-dom@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
@ -7944,7 +7944,7 @@ react-select@^3.1.0:
react-input-autosize "^2.2.2"
react-transition-group "^4.3.0"
react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1:
react-test-renderer@^16.0.0-0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==
@ -7954,6 +7954,16 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1:
react-is "^16.8.6"
scheduler "^0.19.1"
react-test-renderer@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==
dependencies:
object-assign "^4.1.1"
prop-types "^15.6.2"
react-is "^16.8.6"
scheduler "^0.19.1"
react-timeago@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/react-timeago/-/react-timeago-4.4.0.tgz#4520dd9ba63551afc4d709819f52b14b9343ba2b"
@ -7969,10 +7979,10 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
react@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"