mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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