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 (
+