From 46843aecf186a2bff77f83a32ad1cca6efab12d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Wed, 6 Jan 2021 21:41:56 +0100 Subject: [PATCH] fix: remove use of input stores --- frontend/package.json | 6 +- frontend/src/component/input-helpers.js | 81 ------------ .../src/component/strategies/add-container.js | 62 --------- .../component/strategies/edit-container.js | 60 --------- .../component/strategies/form-container.js | 120 ++++++++++++++++++ .../{add-strategy.jsx => from-strategy.jsx} | 56 +++----- .../strategies/strategy-details-component.jsx | 4 +- frontend/src/page/strategies/create.js | 2 +- frontend/src/store/index.js | 2 - frontend/src/store/input-actions.js | 28 ---- frontend/src/store/input-store.js | 114 ----------------- frontend/yarn.lock | 28 ++-- 12 files changed, 165 insertions(+), 398 deletions(-) delete mode 100644 frontend/src/component/input-helpers.js delete mode 100644 frontend/src/component/strategies/add-container.js delete mode 100644 frontend/src/component/strategies/edit-container.js create mode 100644 frontend/src/component/strategies/form-container.js rename frontend/src/component/strategies/{add-strategy.jsx => from-strategy.jsx} (77%) delete mode 100644 frontend/src/store/input-actions.js delete mode 100644 frontend/src/store/input-store.js diff --git a/frontend/package.json b/frontend/package.json index 8dcb2e6aea..341ac3eaa9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/component/input-helpers.js b/frontend/src/component/input-helpers.js deleted file mode 100644 index 6f6811cdcb..0000000000 --- a/frontend/src/component/input-helpers.js +++ /dev/null @@ -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 - ); -} diff --git a/frontend/src/component/strategies/add-container.js b/frontend/src/component/strategies/add-container.js deleted file mode 100644 index b6e74ad1f3..0000000000 --- a/frontend/src/component/strategies/add-container.js +++ /dev/null @@ -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); diff --git a/frontend/src/component/strategies/edit-container.js b/frontend/src/component/strategies/edit-container.js deleted file mode 100644 index adcb05da7d..0000000000 --- a/frontend/src/component/strategies/edit-container.js +++ /dev/null @@ -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); diff --git a/frontend/src/component/strategies/form-container.js b/frontend/src/component/strategies/form-container.js new file mode 100644 index 0000000000..4f13e21023 --- /dev/null +++ b/frontend/src/component/strategies/form-container.js @@ -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 ( + + ); + } +} +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; diff --git a/frontend/src/component/strategies/add-strategy.jsx b/frontend/src/component/strategies/from-strategy.jsx similarity index 77% rename from frontend/src/component/strategies/add-strategy.jsx rename to frontend/src/component/strategies/from-strategy.jsx index 642125db53..8acacdb349 100644 --- a/frontend/src/component/strategies/add-strategy.jsx +++ b/frontend/src/component/strategies/from-strategy.jsx @@ -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 /> ); @@ -88,17 +80,17 @@ const CreateHeader = () => ( ); -const Parameters = ({ input = [], count = 0, updateInList }) => ( +const Parameters = ({ input = [], count = 0, updateParameter }) => (
{gerArrayWithEntries(count).map((v, i) => ( - updateInList('parameters', i, v, true)} index={i} input={input[i]} /> + updateParameter(i, v, true)} index={i} input={input[i]} /> ))}
); 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 ( - {editmode ? : } + {editMode ? : } -
+
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} /> - + { e.preventDefault(); - incValue('_params'); + appParameter(); }} />{' '}  Add parameter
- +
diff --git a/frontend/src/component/strategies/strategy-details-component.jsx b/frontend/src/component/strategies/strategy-details-component.jsx index 8110095789..7aee5d48f6 100644 --- a/frontend/src/component/strategies/strategy-details-component.jsx +++ b/frontend/src/component/strategies/strategy-details-component.jsx @@ -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 ; + return ; } else { return ( ; diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index d49f6cf395..ca63f8db01 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -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, diff --git a/frontend/src/store/input-actions.js b/frontend/src/store/input-actions.js deleted file mode 100644 index fb4aa87a22..0000000000 --- a/frontend/src/store/input-actions.js +++ /dev/null @@ -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; diff --git a/frontend/src/store/input-store.js b/frontend/src/store/input-store.js deleted file mode 100644 index d22addc586..0000000000 --- a/frontend/src/store/input-store.js +++ /dev/null @@ -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; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 89f8162a5b..d5612067e4 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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"