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:
parent
d3d833e105
commit
77c3fbfb02
2
packages/unleash-frontend-next/.eslintignore
Normal file
2
packages/unleash-frontend-next/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
bundle.js
|
14
packages/unleash-frontend-next/README.md
Normal file
14
packages/unleash-frontend-next/README.md
Normal 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!
|
43
packages/unleash-frontend-next/mock-api.json
Normal file
43
packages/unleash-frontend-next/mock-api.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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}>
|
||||
|
56
packages/unleash-frontend-next/src/store/actions.js
Normal file
56
packages/unleash-frontend-next/src/store/actions.js
Normal 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)));
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -56,4 +56,12 @@ module.exports = {
|
||||
// stuff not in node_modules can be resolved here.
|
||||
},
|
||||
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/features': {
|
||||
target: 'http://localhost:3001/',
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user