diff --git a/public/js/__tests__/components/feature/ArchiveFeatureComponent-test.js b/public/js/__tests__/components/feature/ArchiveFeatureComponent-test.js index 871071f51f..a219abd55c 100644 --- a/public/js/__tests__/components/feature/ArchiveFeatureComponent-test.js +++ b/public/js/__tests__/components/feature/ArchiveFeatureComponent-test.js @@ -17,7 +17,7 @@ describe("FeatureForm", function () { ]; Server.getArchivedFeatures.mockImplementation(function(cb) { - cb(archivedToggles); + cb(null, archivedToggles); }); FeatureToggleStore.initStore(archivedToggles); diff --git a/public/js/__tests__/components/feature/FeatureForm-test.js b/public/js/__tests__/components/feature/FeatureForm-test.js index d42a0c9639..93fe28f78b 100644 --- a/public/js/__tests__/components/feature/FeatureForm-test.js +++ b/public/js/__tests__/components/feature/FeatureForm-test.js @@ -3,31 +3,19 @@ jest.dontMock("../../../components/feature/FeatureForm"); var React = require("react/addons"); var TestUtils = React.addons.TestUtils; var FeatureForm = require("../../../components/feature/FeatureForm"); -var strategyStore = require("../../../stores/StrategyStore"); describe("FeatureForm", function () { var Component; - beforeEach(function() { - strategyStore.getStrategies.mockImplementation(function() { - return { - then: function (callback) { - return callback({ - strategies: [ - { name: "default"} - ] - }); - } - }; - }); - }); - + var strategies = [ + { name: "default"} + ]; afterEach(function() { React.unmountComponentAtNode(document.body); }); describe("new", function () { it("should render empty form", function() { - Component = TestUtils .renderIntoDocument(); + Component = TestUtils .renderIntoDocument(); var name = Component.getDOMNode().querySelectorAll("input"); expect(name[0].value).toEqual(undefined); }); @@ -37,11 +25,11 @@ describe("FeatureForm", function () { var feature = {name: "Test", strategy: "unknown"}; it("should show unknown strategy as deleted", function () { - Component = TestUtils .renderIntoDocument(); + Component = TestUtils .renderIntoDocument(); var strategySelect = Component.getDOMNode().querySelector("select"); expect(strategySelect.value).toEqual("unknown (deleted)"); }); }); -}); \ No newline at end of file +}); diff --git a/public/js/__tests__/components/feature/FeatureList-test.js b/public/js/__tests__/components/feature/FeatureList-test.js index b86230a24b..7c970c66fd 100644 --- a/public/js/__tests__/components/feature/FeatureList-test.js +++ b/public/js/__tests__/components/feature/FeatureList-test.js @@ -13,7 +13,10 @@ describe("FeatureList", function () { { name: "featureX", strategy: "other" }, { name: "group.featureY", strategy: "default" } ]; - Component = TestUtils .renderIntoDocument(); + var strategies=[ + { name: "default"} + ]; + Component = TestUtils .renderIntoDocument(); }); afterEach(function() { @@ -52,4 +55,4 @@ describe("FeatureList", function () { expect(features[0].textContent).toMatch(searchString); }); -}); \ No newline at end of file +}); diff --git a/public/js/components/feature/Feature.jsx b/public/js/components/feature/Feature.jsx index bad6ccd442..40b93747e3 100644 --- a/public/js/components/feature/Feature.jsx +++ b/public/js/components/feature/Feature.jsx @@ -44,7 +44,11 @@ var Feature = React.createClass({ return ( - + ); @@ -110,4 +114,4 @@ var Feature = React.createClass({ }); -module.exports = Feature; \ No newline at end of file +module.exports = Feature; diff --git a/public/js/components/feature/FeatureForm.jsx b/public/js/components/feature/FeatureForm.jsx index 26cb524186..87898da5dc 100644 --- a/public/js/components/feature/FeatureForm.jsx +++ b/public/js/components/feature/FeatureForm.jsx @@ -1,22 +1,16 @@ var React = require('react'); var TextInput = require('../form/TextInput'); -var strategyStore = require('../../stores/StrategyStore'); var FeatureForm = React.createClass({ getInitialState: function() { - return { - strategyOptions: [], - requiredParams: [], - currentStrategy: this.props.feature ? this.props.feature.strategy : "default" - }; + return { + strategyOptions: this.props.strategies, + requiredParams: [], + currentStrategy: this.props.feature ? this.props.feature.strategy : "default" + }; }, componentWillMount: function() { - strategyStore.getStrategies().then(this.handleStrategyResponse); - }, - - handleStrategyResponse: function(response) { - this.setState({strategyOptions: response.strategies}); if(this.props.feature) { this.setSelectedStrategy(this.props.feature.strategy); } @@ -41,9 +35,7 @@ var FeatureForm = React.createClass({ })[0]; if(selectedStrategy) { - if(selectedStrategy.parametersTemplate) { - this.setStrategyParams(selectedStrategy); - } + this.setStrategyParams(selectedStrategy); } else { var updatedStrategyName = name + " (deleted)"; this.setState({ @@ -64,9 +56,9 @@ var FeatureForm = React.createClass({ render: function() { var feature = this.props.feature || { - name: '', - strategy: 'default', - enabled: false + name: '', + strategy: 'default', + enabled: false }; var idPrefix = this.props.feature ? this.props.feature.name : 'new'; @@ -131,9 +123,9 @@ var FeatureForm = React.createClass({ renderStrategyOptions: function() { return this.state.strategyOptions.map(function(strategy) { return ( - + ); }.bind(this)); }, @@ -178,4 +170,4 @@ var FeatureForm = React.createClass({ } }); -module.exports = FeatureForm; \ No newline at end of file +module.exports = FeatureForm; diff --git a/public/js/components/feature/FeatureList.jsx b/public/js/components/feature/FeatureList.jsx index f62a9583bf..1e407aae83 100644 --- a/public/js/components/feature/FeatureList.jsx +++ b/public/js/components/feature/FeatureList.jsx @@ -5,7 +5,8 @@ var noop = function() {}; var FeatureList = React.createClass({ propTypes: { - features: React.PropTypes.array.isRequired + features: React.PropTypes.array.isRequired, + strategies: React.PropTypes.array.isRequired }, getDefaultProps: function() { @@ -47,7 +48,9 @@ var FeatureList = React.createClass({ key={feature.name} feature={feature} onChange={this.props.onFeatureChanged} - onArchive={this.props.onFeatureArchive}/> + onArchive={this.props.onFeatureArchive} + strategies={this.props.strategies} + /> ); }.bind(this)); @@ -83,4 +86,4 @@ var FeatureList = React.createClass({ } }); -module.exports = FeatureList; \ No newline at end of file +module.exports = FeatureList; diff --git a/public/js/components/feature/FeatureTogglesComponent.jsx b/public/js/components/feature/FeatureTogglesComponent.jsx index b1023debac..683d362d3a 100644 --- a/public/js/components/feature/FeatureTogglesComponent.jsx +++ b/public/js/components/feature/FeatureTogglesComponent.jsx @@ -4,6 +4,7 @@ var FeatureForm = require('./FeatureForm'); var FeatureActions = require('../../stores/FeatureToggleActions'); var ErrorActions = require('../../stores/ErrorActions'); var FeatureToggleStore = require('../../stores/FeatureToggleStore'); +var StrategyStore = require('../../stores/StrategyStore'); var FeatureTogglesComponent = React.createClass({ getInitialState: function() { @@ -59,13 +60,17 @@ var FeatureTogglesComponent = React.createClass({ onFeatureArchive={this.archiveFeature} onFeatureSubmit={this.createFeature} onFeatureCancel={this.cancelNewFeature} - onNewFeature={this.newFeature} /> + onNewFeature={this.newFeature} + strategies={StrategyStore.getStrategies()} /> ); }, renderCreateView: function() { - return ; + return ; }, renderCreateButton: function() { diff --git a/public/js/components/strategy/StrategiesComponent.jsx b/public/js/components/strategy/StrategiesComponent.jsx index 1a625d61d5..e913f6c03b 100644 --- a/public/js/components/strategy/StrategiesComponent.jsx +++ b/public/js/components/strategy/StrategiesComponent.jsx @@ -1,36 +1,27 @@ -var React = require('react'); -var StrategyList = require('./StrategyList'); -var StrategyForm = require('./StrategyForm'); -var strategyStore = require('../../stores/StrategyStore'); -var ErrorActions = require('../../stores/ErrorActions'); +var React = require('react'); +var StrategyList = require('./StrategyList'); +var StrategyForm = require('./StrategyForm'); +var StrategyStore = require('../../stores/StrategyStore'); +var StrategyActions = require('../../stores/StrategyActions'); var StrategiesComponent = React.createClass({ getInitialState: function() { return { createView: false, - strategies: [] + strategies: StrategyStore.getStrategies() }; }, - componentDidMount: function () { - this.fetchStrategies(); + onStoreChange: function() { + this.setState({ + strategies: StrategyStore.getStrategies() + }); }, - - fetchStrategies: function() { - strategyStore.getStrategies() - .then(function(res) { - this.setState({strategies: res.strategies}); - }.bind(this)) - .catch(this.initError); - + componentDidMount: function() { + this.unsubscribe = StrategyStore.listen(this.onStoreChange); }, - - initError: function() { - this.onError("Could not load inital strategies from server"); - }, - - onError: function(error) { - ErrorActions.error(error); + componentWillUnmount: function() { + this.unsubscribe(); }, onNewStrategy: function() { @@ -42,32 +33,19 @@ var StrategiesComponent = React.createClass({ }, onSave: function(strategy) { - function handleSuccess() { - var strategies = this.state.strategies.concat([strategy]); - - this.setState({ - createView: false, - strategies: strategies - }); - - console.log("Saved strategy: ", strategy); - } - - strategyStore.createStrategy(strategy) - .then(handleSuccess.bind(this)) - .catch(this.onError); + StrategyActions.create.triggerPromise(strategy) + .then(this.onCancelNewStrategy); }, onRemove: function(strategy) { - strategyStore.removeStrategy(strategy) - .then(this.fetchStrategies) - .catch(this.onError); + StrategyActions.remove.triggerPromise(strategy); }, render: function() { return (
- {this.state.createView ? this.renderCreateView() : this.renderCreateButton()} + {this.state.createView ? + this.renderCreateView() : this.renderCreateButton()}
); - }, + }, - renderCreateButton: function() { - return ( - - ); - } -}); + renderCreateButton: function() { + return ( + + ); + } + }); -module.exports = StrategiesComponent; + module.exports = StrategiesComponent; diff --git a/public/js/stores/StrategyAPI.js b/public/js/stores/StrategyAPI.js new file mode 100644 index 0000000000..9ddbcb5ca8 --- /dev/null +++ b/public/js/stores/StrategyAPI.js @@ -0,0 +1,52 @@ +var reqwest = require('reqwest'); + +var TYPE = 'json'; +var CONTENT_TYPE = 'application/json'; + +var StrategyAPI = { + createStrategy: function (strategy, cb) { + reqwest({ + url: 'strategies', + method: 'post', + type: TYPE, + contentType: CONTENT_TYPE, + data: JSON.stringify(strategy), + error: function(error) { + cb(error); + }, + success: function() { + cb(null, strategy); + } + }); + }, + + removeStrategy: function (strategy, cb) { + reqwest({ + url: 'strategies/'+strategy.name, + method: 'delete', + type: TYPE, + error: function(error) { + cb(error); + }, + success: function() { + cb(null, strategy); + } + }); + }, + + getStrategies: function (cb) { + reqwest({ + url: 'strategies', + method: 'get', + type: TYPE, + error: function(error) { + cb(error); + }, + success: function(data) { + cb(null, data.strategies); + } + }); + } +}; + +module.exports = StrategyAPI; diff --git a/public/js/stores/StrategyActions.js b/public/js/stores/StrategyActions.js new file mode 100644 index 0000000000..8dcc5f391a --- /dev/null +++ b/public/js/stores/StrategyActions.js @@ -0,0 +1,29 @@ +var Reflux = require("reflux"); +var StrategyAPI = require('./StrategyAPI'); + +var StrategyActions = Reflux.createActions({ + 'create': { asyncResult: true }, + 'remove': { asyncResult: true }, +}); + +StrategyActions.create.listen(function(feature){ + StrategyAPI.createStrategy(feature, function(err) { + if(err) { + this.failed(err); + } else { + this.completed(feature); + } + }.bind(this)); +}); + +StrategyActions.remove.listen(function(feature){ + StrategyAPI.removeStrategy(feature, function(err) { + if(err) { + this.failed(err); + } else { + this.completed(feature); + } + }.bind(this)); +}); + +module.exports = StrategyActions; diff --git a/public/js/stores/StrategyStore.js b/public/js/stores/StrategyStore.js index 09388e6e34..f25c853fc0 100644 --- a/public/js/stores/StrategyStore.js +++ b/public/js/stores/StrategyStore.js @@ -1,34 +1,57 @@ -var reqwest = require('reqwest'); +var Reflux = require('reflux'); +var ErrorActions = require('./ErrorActions'); +var StrategyActions = require('./StrategyActions'); +var StrategyAPI = require('./StrategyAPI'); +var filter = require('lodash/collection/filter'); -var TYPE = 'json'; -var CONTENT_TYPE = 'application/json'; +var _strategies = []; -var StrategyStore = { - createStrategy: function (strategy) { - return reqwest({ - url: 'strategies', - method: 'post', - type: TYPE, - contentType: CONTENT_TYPE, - data: JSON.stringify(strategy) - }); +// Creates a DataStore +var StrategyStore = Reflux.createStore({ + + // Initial setup + init: function() { + this.listenTo(StrategyActions.create.completed, this.onCreate); + this.listenTo(StrategyActions.remove.completed, this.onRemove); + this.loadDataFromServer(); }, - removeStrategy: function (strategy) { - return reqwest({ - url: 'strategies/'+strategy.name, - method: 'delete', - type: TYPE - }); + loadDataFromServer: function() { + //TODO: this should not be part of the store! + StrategyAPI.getStrategies(function(err, strategies) { + + if(err) { + ErrorActions.error(err); + return; + } else { + this.setStrategies(strategies); + } + }.bind(this)); }, - getStrategies: function () { - return reqwest({ - url: 'strategies', - method: 'get', - type: TYPE + onCreate: function(strategy) { + this.setStrategies(_strategies.concat([strategy])); + }, + + onRemove: function(strategy) { + var strategies = filter(_strategies, function(item) { + return item.name !== strategy.name; }); + this.setStrategies(strategies); + }, + + setStrategies: function(strategies) { + _strategies = strategies; + this.trigger(_strategies); + }, + + getStrategies: function() { + return _strategies; + }, + + initStore: function(strategies) { + _strategies = strategies; } -}; +}); module.exports = StrategyStore;