1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

Added support for fetching data.

- use "json-server" for serving mocked API responses.
- setup redux to fetch responses when featureToggle list is mounted.
This commit is contained in:
Ivar 2016-09-28 23:02:23 +02:00 committed by Ivar Conradi Østhus
parent d3d833e105
commit 77c3fbfb02
14 changed files with 203 additions and 64 deletions

View File

@ -0,0 +1,2 @@
node_modules
bundle.js

View File

@ -0,0 +1,14 @@
## Start developing:
1. start mock-api:
```bash
npm run start:api
```
2. start webpack-dev-server with hot-reload:
```bash
npm run start
```
Happy coding!

View File

@ -0,0 +1,43 @@
{
"features": {
"version": 1,
"features": [
{
"name": "Feature.A",
"description": "lorem ipsum",
"enabled": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"strategy": "default",
"parameters": {}
},
{
"name": "Feature.B",
"description": "lorem ipsum",
"enabled": true,
"strategies": [
{
"name": "ActiveForUserWithId",
"parameters": {
"userIdList": "123,221,998"
}
},
{
"name": "GradualRolloutRandom",
"parameters": {
"percentage": "10"
}
}
],
"strategy": "ActiveForUserWithId",
"parameters": {
"userIdList": "123,221,998"
}
}
]
}
}

View File

@ -24,6 +24,8 @@
"scripts": {
"build": "webpack -p",
"start": "webpack-dev-server --config webpack.config.js --hot --progress --colors --port 3000",
"start:api": "json-server --watch mock-api.json -p 3001",
"lint": "eslint . --ext=js,jsx",
"test": "echo 'no test'",
"test:ci": "npm run test",
"prepublish": "npm run build"
@ -38,7 +40,8 @@
"react-redux": "^4.4.5",
"react-router": "^2.8.0",
"react-toolbox": "^1.2.1",
"redux": "^3.6.0"
"redux": "^3.6.0",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.14.0",
@ -49,7 +52,12 @@
"babel-preset-stage-0": "^6.5.0",
"babel-preset-stage-2": "^6.13.0",
"css-loader": "^0.25.0",
"eslint": "^3.4.0",
"eslint-config-finn": "1.0.0-alpha.11",
"eslint-config-finn-react": "^1.0.0-alpha.2",
"eslint-plugin-react": "^6.2.0",
"extract-text-webpack-plugin": "^1.0.1",
"json-server": "^0.8.21",
"node-sass": "~3.7.0",
"postcss-loader": "^0.13.0",
"redux-devtools": "^3.3.1",

View File

@ -1,24 +1,12 @@
{
"extends": [
"finn",
"finn-react"
"finn-react",
"finn/es-modules"
],
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"rules": {
"react/sort-comp": "off"
},
"ecmaFeatures": {
"sourceType": "module"
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
}
}
}

View File

@ -1,11 +0,0 @@
let nextFeatureId = 0;
export const addFeatureToggle = (featureName) => ({
type: 'ADD_FEATURE_TOGGLE',
id: nextFeatureId++,
featureName,
});
export const toggleFeature = (id) => ({
type: 'TOGGLE_FEATURE_TOGGLE',
id,
});

View File

@ -1,6 +1,6 @@
import React from 'react';
import { React, PropTypes } from 'react';
import { connect } from 'react-redux';
import { addFeatureToggle } from '../../action';
import { addFeatureToggle } from '../../store/actions';
let AddFeatureToggle = ({ dispatch }) => {
@ -28,4 +28,9 @@ let AddFeatureToggle = ({ dispatch }) => {
};
AddFeatureToggle = connect()(AddFeatureToggle);
AddFeatureToggle.propTypes = {
dispatch: PropTypes.func.isRequired,
};
export default AddFeatureToggle;

View File

@ -1,8 +1,10 @@
/* eslint no-shadow: ["error", { "allow": ["name"] }]*/
import React, { PropTypes } from 'react';
const Feature = ({ onClick, featureName, enabled }) => (
const Feature = ({ onClick, name, enabled }) => (
<li>
{featureName} is {enabled ? 'enabled ' : 'disabled '}
{name} is {enabled ? 'enabled ' : 'disabled '}
<a onClick={onClick} href="#">toggle</a>
</li>
);
@ -10,7 +12,7 @@ const Feature = ({ onClick, featureName, enabled }) => (
Feature.propTypes = {
onClick: PropTypes.func.isRequired,
enabled: PropTypes.bool.isRequired,
featureName: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
export default Feature;

View File

@ -1,21 +1,33 @@
import React, { PropTypes } from 'react';
import Feature from './Feature';
const FeatureList = ({ features, onFeatureClick }) => (
<ul>
{features.map(featureToggle =>
<Feature
key={featureToggle.id}
{...featureToggle}
onClick={() => onFeatureClick(featureToggle.id)}
/>
)}
</ul>
);
export default class FeatureList extends React.Component {
constructor () {
super();
}
FeatureList.propTypes = {
onFeatureClick: PropTypes.func.isRequired,
features: PropTypes.array.isRequired,
};
static propTypes () {
return {
onFeatureClick: PropTypes.func.isRequired,
features: PropTypes.array.isRequired,
fetchFeatureToggles: PropTypes.array.isRequired,
};
}
export default FeatureList;
componentDidMount () {
this.props.fetchFeatureToggles();
}
render () {
const onFeatureClick = this.props.onFeatureClick;
const features = this.props.features.map(featureToggle =>
<Feature key={featureToggle.name}
{...featureToggle}
onClick={() => onFeatureClick(featureToggle.name)}
/>);
return (
<ul>{features}</ul>
);
}
}

View File

@ -1,17 +1,15 @@
import { connect } from 'react-redux';
import { toggleFeature } from '../../action';
import { toggleFeature, fetchFeatureToggles } from '../../store/actions';
import FeatureList from './FeatureList';
const mapStateToProps = (state) => ({
features: state.features,
});
const mapDispatchToProps = (dispatch) => ({
onFeatureClick: (id) => {
dispatch(toggleFeature(id));
},
});
const mapDispatchToProps = {
onFeatureClick: toggleFeature,
fetchFeatureToggles,
};
const FeatureListContainer = connect(
mapStateToProps,

View File

@ -2,7 +2,8 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import store from './store';
import App from './App';
@ -12,7 +13,12 @@ import Strategies from './page/strategies';
import HistoryPage from './page/history';
import Archive from './page/archive';
const unleashStore = createStore(store);
const unleashStore = createStore(
store,
applyMiddleware(
thunkMiddleware
)
);
ReactDOM.render(
<Provider store={unleashStore}>

View File

@ -0,0 +1,56 @@
export const ADD_FEATURE_TOGGLE = 'ADD_FEATURE_TOGGLE';
export const TOGGLE_FEATURE_TOGGLE = 'TOGGLE_FEATURE_TOGGLE';
export const REQUEST_FEATURE_TOGGLES = 'REQUEST_FEATURE_TOGGLES';
export const RECEIVE_FEATURE_TOGGLES = 'RECEIVE_FEATURE_TOGGLES';
export const ERROR_RECEIVE_FEATURE_TOGGLES = 'ERROR_RECEIVE_FEATURE_TOGGLES';
export const addFeatureToggle = (featureName) => ({
type: 'ADD_FEATURE_TOGGLE',
name: featureName,
});
export const toggleFeature = (featureName) => ({
type: 'TOGGLE_FEATURE_TOGGLE',
name: featureName,
});
function requestFeatureToggles () {
return {
type: REQUEST_FEATURE_TOGGLES,
};
}
function receiveFeatureToggles (json) {
return {
type: RECEIVE_FEATURE_TOGGLES,
featureToggles: json.features.map(features => features),
receivedAt: Date.now(),
};
}
function errorReceiveFeatureToggles (statusCode) {
return {
type: ERROR_RECEIVE_FEATURE_TOGGLES,
statusCode,
receivedAt: Date.now(),
};
}
export function fetchFeatureToggles () {
return dispatch => {
dispatch(requestFeatureToggles());
return fetch('/features')
.then(response => {
if (response.ok) {
return response.json();
} else {
let error = new Error('failed fetching');
error.status = response.status;
throw error;
}
})
.then(json => dispatch(receiveFeatureToggles(json)))
.catch(error => dispatch(errorReceiveFeatureToggles(error)));
};
}

View File

@ -1,13 +1,18 @@
import {
ADD_FEATURE_TOGGLE,
TOGGLE_FEATURE_TOGGLE,
RECEIVE_FEATURE_TOGGLES,
} from './actions';
const feature = (state = {}, action) => {
switch (action.type) {
case 'ADD_FEATURE_TOGGLE':
case ADD_FEATURE_TOGGLE:
return {
id: action.id,
featureName: action.featureName,
name: action.name,
enabled: false,
};
case 'TOGGLE_FEATURE_TOGGLE':
if (state.id !== action.id) {
case TOGGLE_FEATURE_TOGGLE:
if (state.name !== action.name) {
return state;
}
@ -20,17 +25,20 @@ const feature = (state = {}, action) => {
}
};
const features = (state = [{ id: 1, featureName: 'test', enabled: true }], action) => {
const features = (state = [], action) => {
switch (action.type) {
case 'ADD_FEATURE_TOGGLE':
case ADD_FEATURE_TOGGLE:
return [
...state,
feature(undefined, action),
];
case 'TOGGLE_FEATURE_TOGGLE':
case TOGGLE_FEATURE_TOGGLE:
return state.map(t =>
feature(t, action)
);
case RECEIVE_FEATURE_TOGGLES: {
return action.featureToggles;
}
default:
return state;
}

View File

@ -56,4 +56,12 @@ module.exports = {
// stuff not in node_modules can be resolved here.
},
devServer: {
proxy: {
'/features': {
target: 'http://localhost:3001/',
secure: false,
},
},
},
};