mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge pull request #121 from corinnekrych/archive.revisited
Archive.revisited
This commit is contained in:
		
						commit
						4b64762671
					
				
							
								
								
									
										1
									
								
								frontend/src/__mocks__/react-mdl.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								frontend/src/__mocks__/react-mdl.js
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,6 @@ | ||||
| module.exports = { | ||||
|     Card: 'react-mdl-Card', | ||||
|     CardActions: 'react-mdl-CardActions', | ||||
|     CardTitle: 'react-mdl-CardTitle', | ||||
|     CardText: 'react-mdl-CardText', | ||||
|     CardMenu: 'react-mdl-CardMenu', | ||||
|  | ||||
| @ -192,11 +192,11 @@ export default class App extends Component { | ||||
|                                 <FooterSection type="middle"> | ||||
|                                     <FooterDropDownSection title="Menu"> | ||||
|                                         <FooterLinkList> | ||||
|                                             {createListItem('/features', 'Feature Toggles')} | ||||
|                                             {createListItem('/strategies', 'Strategies')} | ||||
|                                             {createListItem('/history', 'Event History')} | ||||
|                                             {createListItem('/archive', 'Archived Toggles')} | ||||
|                                             {createListItem('/applications', 'Applications')} | ||||
|                                             {createListItem('/features', 'Feature Toggles', '')} | ||||
|                                             {createListItem('/strategies', 'Strategies', '')} | ||||
|                                             {createListItem('/history', 'Event History', '')} | ||||
|                                             {createListItem('/archive', 'Archived Toggles', '')} | ||||
|                                             {createListItem('/applications', 'Applications', '')} | ||||
|                                             <a href="/api/admin/user/logout">Sign out</a> | ||||
|                                         </FooterLinkList> | ||||
|                                     </FooterDropDownSection> | ||||
|  | ||||
| @ -1,5 +0,0 @@ | ||||
| { | ||||
|     "env": { | ||||
|         "jest": true | ||||
|     } | ||||
| } | ||||
| @ -1,149 +0,0 @@ | ||||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
| 
 | ||||
| exports[`renders correctly with archived toggles 1`] = ` | ||||
| <react-mdl-Card | ||||
|   className="fullwidth" | ||||
|   shadow={0} | ||||
| > | ||||
|   <div> | ||||
|     <div | ||||
|       style={ | ||||
|         Object { | ||||
|           "position": "relative", | ||||
|         } | ||||
|       } | ||||
|     > | ||||
|       <react-mdl-List> | ||||
|         <react-mdl-ListItem | ||||
|           className="archiveList" | ||||
|         > | ||||
|           <span | ||||
|             className="listItemToggle" | ||||
|           > | ||||
|             Toggle name | ||||
|           </span> | ||||
|           <span | ||||
|             className="listItemRevive" | ||||
|           > | ||||
|             Revive | ||||
|           </span> | ||||
|         </react-mdl-ListItem> | ||||
|         <hr /> | ||||
|         <react-mdl-List> | ||||
|           <react-mdl-ListItem | ||||
|             twoLine={true} | ||||
|           > | ||||
|             <react-mdl-ListItemAction> | ||||
|               <react-mdl-Icon | ||||
|                 name="keyboard_arrow_right" | ||||
|               /> | ||||
|             </react-mdl-ListItemAction> | ||||
|             <react-mdl-ListItemContent> | ||||
|               <a | ||||
|                 className="listLink truncate" | ||||
|                 onClick={[Function]} | ||||
|                 style={Object {}} | ||||
|               > | ||||
|                 adin-pay-confirm-disabled | ||||
|                 <span> | ||||
|                   <span | ||||
|                     className="strategiesList hideLt920" | ||||
|                   > | ||||
|                     <react-mdl-Chip | ||||
|                       className="strategyChip" | ||||
|                     > | ||||
|                       default | ||||
|                     </react-mdl-Chip> | ||||
|                   </span> | ||||
|                 </span> | ||||
|                 <div | ||||
|                   className="mdl-list__item-sub-title" | ||||
|                 > | ||||
|                   Disables the confirm-functionality from API | ||||
|                 </div> | ||||
|               </a> | ||||
|             </react-mdl-ListItemContent> | ||||
|             <react-mdl-ListItemAction | ||||
|               onClick={[Function]} | ||||
|             > | ||||
|               <react-mdl-Icon | ||||
|                 name="undo" | ||||
|               /> | ||||
|             </react-mdl-ListItemAction> | ||||
|           </react-mdl-ListItem> | ||||
|           <react-mdl-ListItem | ||||
|             twoLine={true} | ||||
|           > | ||||
|             <react-mdl-ListItemAction> | ||||
|               <react-mdl-Icon | ||||
|                 name="keyboard_arrow_right" | ||||
|               /> | ||||
|             </react-mdl-ListItemAction> | ||||
|             <react-mdl-ListItemContent> | ||||
|               <a | ||||
|                 className="listLink truncate" | ||||
|                 onClick={[Function]} | ||||
|                 style={Object {}} | ||||
|               > | ||||
|                 adin-pay-platform-sch-payment | ||||
|                 <span> | ||||
|                   <span | ||||
|                     className="strategiesList hideLt920" | ||||
|                   > | ||||
|                     <react-mdl-Chip | ||||
|                       className="strategyChip" | ||||
|                     > | ||||
|                       default | ||||
|                     </react-mdl-Chip> | ||||
|                   </span> | ||||
|                 </span> | ||||
|                 <div | ||||
|                   className="mdl-list__item-sub-title" | ||||
|                 > | ||||
|                   Enables use of schibsted payment from order-payment-management | ||||
|                 </div> | ||||
|               </a> | ||||
|             </react-mdl-ListItemContent> | ||||
|             <react-mdl-ListItemAction | ||||
|               onClick={[Function]} | ||||
|             > | ||||
|               <react-mdl-Icon | ||||
|                 name="undo" | ||||
|               /> | ||||
|             </react-mdl-ListItemAction> | ||||
|           </react-mdl-ListItem> | ||||
|         </react-mdl-List> | ||||
|       </react-mdl-List> | ||||
|     </div> | ||||
|   </div> | ||||
| </react-mdl-Card> | ||||
| `; | ||||
| 
 | ||||
| exports[`renders correctly with no archived toggles 1`] = ` | ||||
| <react-mdl-Card | ||||
|   className="fullwidth" | ||||
|   shadow={0} | ||||
| > | ||||
|   <div | ||||
|     className="emptyState" | ||||
|   > | ||||
|     <react-mdl-Icon | ||||
|       className="mdl-color-text--grey-300" | ||||
|       name="archive" | ||||
|       style={ | ||||
|         Object { | ||||
|           "fontSize": "56px", | ||||
|         } | ||||
|       } | ||||
|     /> | ||||
|     <br /> | ||||
|     No archived feature toggles, go see  | ||||
|     <a | ||||
|       onClick={[Function]} | ||||
|       style={Object {}} | ||||
|     > | ||||
|       active toggles here | ||||
|     </a> | ||||
|   </div> | ||||
| </react-mdl-Card> | ||||
| `; | ||||
| @ -1,35 +0,0 @@ | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import ArchiveList from '../archive-list-component'; | ||||
| import renderer from 'react-test-renderer'; | ||||
| 
 | ||||
| jest.mock('react-mdl'); | ||||
| 
 | ||||
| const archive = [ | ||||
|     { | ||||
|         name: 'adin-pay-confirm-disabled', | ||||
|         description: 'Disables the confirm-functionality from API', | ||||
|         enabled: false, | ||||
|         strategies: [{ name: 'default', parameters: {} }], | ||||
|         createdAt: '2016-10-25T15:38:28.573Z', | ||||
|         reviveName: 'adin-pay-confirm-disabled', | ||||
|     }, | ||||
|     { | ||||
|         name: 'adin-pay-platform-sch-payment', | ||||
|         description: 'Enables use of schibsted payment from order-payment-management', | ||||
|         enabled: true, | ||||
|         strategies: [{ name: 'default', parameters: {} }], | ||||
|         createdAt: '2016-08-03T12:41:35.631Z', | ||||
|         reviveName: 'adin-pay-platform-sch-payment', | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| test('renders correctly with no archived toggles', () => { | ||||
|     const tree = renderer.create(<ArchiveList fetchArchive={jest.fn()} archive={[]} />).toJSON(); | ||||
|     expect(tree).toMatchSnapshot(); | ||||
| }); | ||||
| 
 | ||||
| test('renders correctly with archived toggles', () => { | ||||
|     const tree = renderer.create(<ArchiveList fetchArchive={jest.fn()} archive={archive} />).toJSON(); | ||||
|     expect(tree).toMatchSnapshot(); | ||||
| }); | ||||
| @ -1,144 +0,0 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Link } from 'react-router'; | ||||
| import { Icon, Card, List, ListItem, ListItemContent, ListItemAction, Chip } from 'react-mdl'; | ||||
| import { styles as commonStyles } from '../common'; | ||||
| import styles from './archive.scss'; | ||||
| 
 | ||||
| class ArchiveList extends Component { | ||||
|     static propTypes = { | ||||
|         name: PropTypes.string, | ||||
|         archive: PropTypes.array, | ||||
|         fetchArchive: PropTypes.func, | ||||
|         revive: PropTypes.func, | ||||
|     }; | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         this.props.fetchArchive(); | ||||
|     } | ||||
|     renderStrategyDetail(feature) { | ||||
|         let strategiesList = ( | ||||
|             <span> | ||||
|                 {feature.strategies.map((s, i) => ( | ||||
|                     <span style={{ marginLeft: `8px` }} key={i}> | ||||
|                         <strong>{s.name}</strong> | ||||
|                         {Object.keys(s.parameters).map((p, j) => <i key={j}> {s.parameters[p]}</i>)} | ||||
|                     </span> | ||||
|                 ))} | ||||
|             </span> | ||||
|         ); | ||||
| 
 | ||||
|         return strategiesList; | ||||
|     } | ||||
|     renderStrategiesInList(feature) { | ||||
|         let display = []; | ||||
|         if (feature.strategies && feature.strategies.length > 0) { | ||||
|             const strategiesToShow = Math.min(feature.strategies.length, 3); | ||||
|             const remainingStrategies = feature.strategies.length - strategiesToShow; | ||||
| 
 | ||||
|             const strategyChips = | ||||
|                 feature.strategies && | ||||
|                 feature.strategies.slice(0, strategiesToShow).map((s, i) => ( | ||||
|                     <span key={i} className={[styles.strategiesList, commonStyles.hideLt920].join(' ')}> | ||||
|                         <Chip className={styles.strategyChip}>{s.name}</Chip> | ||||
|                     </span> | ||||
|                 )); | ||||
|             const remaining = ( | ||||
|                 <span className={[styles.strategiesList, commonStyles.hideLt920].join(' ')}> | ||||
|                     <Chip className={styles.strategyChip}>+{remainingStrategies}</Chip> | ||||
|                 </span> | ||||
|             ); | ||||
|             if (remainingStrategies > 0) { | ||||
|                 display.push(remaining); | ||||
|             } | ||||
|             display.push(strategyChips); | ||||
|         } | ||||
|         return display; | ||||
|     } | ||||
|     render() { | ||||
|         const { archive, revive } = this.props; | ||||
|         archive.forEach(e => { | ||||
|             e.reviveName = e.name; | ||||
|         }); | ||||
|         return ( | ||||
|             <Card shadow={0} className={commonStyles.fullwidth}> | ||||
|                 {archive && archive.length > 0 ? ( | ||||
|                     <div> | ||||
|                         <div style={{ position: 'relative' }}> | ||||
|                             <List> | ||||
|                                 <ListItem className={styles.archiveList}> | ||||
|                                     <span className={styles.listItemToggle}>Toggle name</span> | ||||
|                                     <span className={styles.listItemRevive}>Revive</span> | ||||
|                                 </ListItem> | ||||
|                                 <hr /> | ||||
|                                 <List> | ||||
|                                     {archive.map((feature, i) => ( | ||||
|                                         <ListItem key={i} twoLine> | ||||
|                                             <ListItemAction> | ||||
|                                                 {this.props.name && feature.name === this.props.name ? ( | ||||
|                                                     <Icon name="keyboard_arrow_down" /> | ||||
|                                                 ) : ( | ||||
|                                                     <Icon name="keyboard_arrow_right" /> | ||||
|                                                 )} | ||||
|                                             </ListItemAction> | ||||
|                                             <ListItemContent> | ||||
|                                                 {this.props.name && feature.name === this.props.name ? ( | ||||
|                                                     <Link | ||||
|                                                         to={`/archive`} | ||||
|                                                         className={[commonStyles.listLink, commonStyles.truncate].join( | ||||
|                                                             ' ' | ||||
|                                                         )} | ||||
|                                                     > | ||||
|                                                         {this.renderStrategiesInList(feature).map((strategyChip, i) => ( | ||||
|                                                             <span key={i}>{strategyChip}</span> | ||||
|                                                         ))} | ||||
| 
 | ||||
|                                                         {feature.name} | ||||
|                                                         <div className={'mdl-list__item-sub-title'}> | ||||
|                                                             {feature.description} | ||||
|                                                         </div> | ||||
|                                                         <div className={'mdl-list__item-sub-title'}> | ||||
|                                                             {this.renderStrategyDetail(feature)} | ||||
|                                                         </div> | ||||
|                                                     </Link> | ||||
|                                                 ) : ( | ||||
|                                                     <Link | ||||
|                                                         to={`/archive/${feature.name}`} | ||||
|                                                         className={[commonStyles.listLink, commonStyles.truncate].join( | ||||
|                                                             ' ' | ||||
|                                                         )} | ||||
|                                                     > | ||||
|                                                         {feature.name} | ||||
| 
 | ||||
|                                                         {this.renderStrategiesInList(feature).map((strategyChip, i) => ( | ||||
|                                                             <span key={i}>{strategyChip}</span> | ||||
|                                                         ))} | ||||
| 
 | ||||
|                                                         <div className={'mdl-list__item-sub-title'}> | ||||
|                                                             {feature.description} | ||||
|                                                         </div> | ||||
|                                                     </Link> | ||||
|                                                 )} | ||||
|                                             </ListItemContent> | ||||
|                                             <ListItemAction onClick={() => revive(feature.name)}> | ||||
|                                                 <Icon name="undo" /> | ||||
|                                             </ListItemAction> | ||||
|                                         </ListItem> | ||||
|                                     ))} | ||||
|                                 </List> | ||||
|                             </List> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 ) : ( | ||||
|                     <div className={commonStyles.emptyState}> | ||||
|                         <Icon name="archive" className="mdl-color-text--grey-300" style={{ fontSize: '56px' }} /> | ||||
|                         <br /> | ||||
|                         No archived feature toggles, go see <Link to="/features">active toggles here</Link> | ||||
|                     </div> | ||||
|                 )} | ||||
|             </Card> | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default ArchiveList; | ||||
| @ -1,15 +1,16 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import ListComponent from './archive-list-component'; | ||||
| import FeatureListComponent from './../feature/list-component'; | ||||
| import { fetchArchive, revive } from './../../store/archive-actions'; | ||||
| import { updateSettingForGroup } from './../../store/settings/actions'; | ||||
| import { mapStateToPropsConfigurable } from '../feature/list-container'; | ||||
| 
 | ||||
| const mapStateToProps = state => { | ||||
|     const archive = state.archive.get('list').toArray(); | ||||
| 
 | ||||
|     return { | ||||
|         archive, | ||||
|     }; | ||||
| const mapStateToProps = mapStateToPropsConfigurable(false); | ||||
| const mapDispatchToProps = { | ||||
|     fetchArchive, | ||||
|     revive, | ||||
|     updateSetting: updateSettingForGroup('feature'), | ||||
| }; | ||||
| 
 | ||||
| const ArchiveListContainer = connect(mapStateToProps, { fetchArchive, revive })(ListComponent); | ||||
| const ArchiveListContainer = connect(mapStateToProps, mapDispatchToProps)(FeatureListComponent); | ||||
| 
 | ||||
| export default ArchiveListContainer; | ||||
|  | ||||
							
								
								
									
										18
									
								
								frontend/src/component/archive/view-container.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/src/component/archive/view-container.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import { fetchArchive, revive } from './../../store/archive-actions'; | ||||
| import ViewToggleComponent from './../feature/view-component'; | ||||
| 
 | ||||
| export default connect( | ||||
|     (state, props) => ({ | ||||
|         features: state.archive.get('list').toArray(), | ||||
|         featureToggle: state.archive | ||||
|             .get('list') | ||||
|             .toArray() | ||||
|             .find(toggle => toggle.name === props.featureToggleName), | ||||
|         activeTab: props.activeTab, | ||||
|     }), | ||||
|     { | ||||
|         fetchArchive, | ||||
|         revive, | ||||
|     } | ||||
| )(ViewToggleComponent); | ||||
| @ -22,6 +22,7 @@ exports[`renders correctly with one feature 1`] = ` | ||||
|   > | ||||
|     <react-mdl-Switch | ||||
|       checked={false} | ||||
|       disabled={true} | ||||
|       onChange={[Function]} | ||||
|       title="Toggle Another" | ||||
|     /> | ||||
| @ -51,5 +52,6 @@ exports[`renders correctly with one feature 1`] = ` | ||||
|       gradualRolloutRandom | ||||
|     </react-mdl-Chip> | ||||
|   </span> | ||||
|   <span /> | ||||
| </react-mdl-ListItem> | ||||
| `; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Link } from 'react-router'; | ||||
| import { Switch, Chip, ListItem } from 'react-mdl'; | ||||
| import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl'; | ||||
| import Progress from './progress'; | ||||
| import { calc, styles as commonStyles } from '../common'; | ||||
| 
 | ||||
| @ -13,12 +13,11 @@ const Feature = ({ | ||||
|     settings, | ||||
|     metricsLastHour = { yes: 0, no: 0, isFallback: true }, | ||||
|     metricsLastMinute = { yes: 0, no: 0, isFallback: true }, | ||||
|     revive, | ||||
| }) => { | ||||
|     const { name, description, enabled, strategies } = feature; | ||||
| 
 | ||||
|     const { showLastHour = false } = settings; | ||||
|     const isStale = showLastHour ? metricsLastHour.isFallback : metricsLastMinute.isFallback; | ||||
| 
 | ||||
|     const percent = | ||||
|         1 * | ||||
|         (showLastHour | ||||
| @ -27,7 +26,6 @@ const Feature = ({ | ||||
| 
 | ||||
|     const strategiesToShow = Math.min(strategies.length, 3); | ||||
|     const remainingStrategies = strategies.length - strategiesToShow; | ||||
| 
 | ||||
|     const strategyChips = | ||||
|         strategies && | ||||
|         strategies.slice(0, strategiesToShow).map((s, i) => ( | ||||
| @ -36,7 +34,7 @@ const Feature = ({ | ||||
|             </Chip> | ||||
|         )); | ||||
|     const summaryChip = remainingStrategies > 0 && <Chip className={styles.strategyChip}>+{remainingStrategies}</Chip>; | ||||
| 
 | ||||
|     const featureUrl = toggleFeature === undefined ? `/archive/strategies/${name}` : `/features/strategies/${name}`; | ||||
|     return ( | ||||
|         <ListItem twoLine> | ||||
|             <span className={styles.listItemMetric}> | ||||
| @ -44,6 +42,7 @@ const Feature = ({ | ||||
|             </span> | ||||
|             <span className={styles.listItemToggle}> | ||||
|                 <Switch | ||||
|                     disabled={toggleFeature !== undefined} | ||||
|                     title={`Toggle ${name}`} | ||||
|                     key="left-actions" | ||||
|                     onChange={() => toggleFeature(name)} | ||||
| @ -51,10 +50,7 @@ const Feature = ({ | ||||
|                 /> | ||||
|             </span> | ||||
|             <span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}> | ||||
|                 <Link | ||||
|                     to={`/features/strategies/${name}`} | ||||
|                     className={[commonStyles.listLink, commonStyles.truncate].join(' ')} | ||||
|                 > | ||||
|                 <Link to={featureUrl} className={[commonStyles.listLink, commonStyles.truncate].join(' ')}> | ||||
|                     {name} | ||||
|                     <span className={['mdl-list__item-sub-title', commonStyles.truncate].join(' ')}>{description}</span> | ||||
|                 </Link> | ||||
| @ -63,6 +59,13 @@ const Feature = ({ | ||||
|                 {strategyChips} | ||||
|                 {summaryChip} | ||||
|             </span> | ||||
|             {revive ? ( | ||||
|                 <ListItemAction onClick={() => revive(feature.name)}> | ||||
|                     <Icon name="undo" /> | ||||
|                 </ListItemAction> | ||||
|             ) : ( | ||||
|                 <span /> | ||||
|             )} | ||||
|         </ListItem> | ||||
|     ); | ||||
| }; | ||||
| @ -73,6 +76,7 @@ Feature.propTypes = { | ||||
|     settings: PropTypes.object, | ||||
|     metricsLastHour: PropTypes.object, | ||||
|     metricsLastMinute: PropTypes.object, | ||||
|     revive: PropTypes.func, | ||||
| }; | ||||
| 
 | ||||
| export default Feature; | ||||
|  | ||||
| @ -45,7 +45,7 @@ const prepare = (methods, dispatch) => { | ||||
|     methods.onCancel = evt => { | ||||
|         evt.preventDefault(); | ||||
|         methods.clear(); | ||||
|         window.history.back(); | ||||
|         hashHistory.push(`/features`); | ||||
|     }; | ||||
| 
 | ||||
|     methods.addStrategy = v => { | ||||
|  | ||||
| @ -0,0 +1,30 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import StrategiesSection from './strategies-section-container'; | ||||
| import { Button, Icon } from 'react-mdl'; | ||||
| 
 | ||||
| class ViewFeatureComponent extends Component { | ||||
|     render() { | ||||
|         const { input, onCancel } = this.props; | ||||
|         const configuredStrategies = input.strategies || []; | ||||
| 
 | ||||
|         return ( | ||||
|             <section style={{ padding: '16px' }}> | ||||
|                 <StrategiesSection configuredStrategies={configuredStrategies} /> | ||||
|                 <br /> | ||||
|                 <Button type="cancel" ripple raised onClick={onCancel} style={{ float: 'right' }}> | ||||
|                     <Icon name="cancel" />    Cancel | ||||
|                 </Button> | ||||
|             </section> | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ViewFeatureComponent.propTypes = { | ||||
|     input: PropTypes.object, | ||||
|     onCancel: PropTypes.func.isRequired, | ||||
|     initCallRequired: PropTypes.bool, | ||||
|     init: PropTypes.func, | ||||
| }; | ||||
| 
 | ||||
| export default ViewFeatureComponent; | ||||
| @ -0,0 +1,40 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import { createMapper, createActions } from '../../input-helpers'; | ||||
| import ViewFeatureToggleComponent from './form-view-feature-component'; | ||||
| import { hashHistory } from 'react-router'; | ||||
| 
 | ||||
| const ID = 'view-feature-toggle'; | ||||
| function getId(props) { | ||||
|     return [ID, props.featureToggle.name]; | ||||
| } | ||||
| // TODO: need to scope to the active featureToggle | ||||
| // best is to emulate the "input-storage"? | ||||
| const mapStateToProps = createMapper({ | ||||
|     id: getId, | ||||
|     getDefault: (state, ownProps) => { | ||||
|         ownProps.featureToggle.strategies.forEach((strategy, index) => { | ||||
|             strategy.id = Math.round(Math.random() * 1000000 * (1 + index)); | ||||
|         }); | ||||
|         return ownProps.featureToggle; | ||||
|     }, | ||||
|     prepare: props => { | ||||
|         props.editmode = true; | ||||
|         return props; | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| const prepare = methods => { | ||||
|     methods.onCancel = evt => { | ||||
|         evt.preventDefault(); | ||||
|         methods.clear(); | ||||
|         hashHistory.push(`/archive`); | ||||
|     }; | ||||
|     return methods; | ||||
| }; | ||||
| 
 | ||||
| const actions = createActions({ | ||||
|     id: getId, | ||||
|     prepare, | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, actions)(ViewFeatureToggleComponent); | ||||
| @ -5,7 +5,7 @@ import { Menu, MenuItem, IconButton } from 'react-mdl'; | ||||
| class AddStrategy extends React.Component { | ||||
|     static propTypes = { | ||||
|         strategies: PropTypes.array.isRequired, | ||||
|         addStrategy: PropTypes.func.isRequired, | ||||
|         addStrategy: PropTypes.func, | ||||
|         fetchStrategies: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
| @ -9,9 +9,9 @@ class StrategiesList extends React.Component { | ||||
|     static propTypes = { | ||||
|         strategies: PropTypes.array.isRequired, | ||||
|         configuredStrategies: PropTypes.array.isRequired, | ||||
|         updateStrategy: PropTypes.func.isRequired, | ||||
|         removeStrategy: PropTypes.func.isRequired, | ||||
|         moveStrategy: PropTypes.func.isRequired, | ||||
|         updateStrategy: PropTypes.func, | ||||
|         removeStrategy: PropTypes.func, | ||||
|         moveStrategy: PropTypes.func, | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
| @ -27,8 +27,8 @@ class StrategiesList extends React.Component { | ||||
|                 key={strategy.id} | ||||
|                 strategy={strategy} | ||||
|                 moveStrategy={moveStrategy} | ||||
|                 removeStrategy={removeStrategy.bind(null, i)} | ||||
|                 updateStrategy={updateStrategy.bind(null, i)} | ||||
|                 removeStrategy={removeStrategy ? removeStrategy.bind(null, i) : null} | ||||
|                 updateStrategy={updateStrategy ? updateStrategy.bind(null, i) : null} | ||||
|                 strategyDefinition={strategies.find(s => s.name === strategy.name)} | ||||
|             /> | ||||
|         )); | ||||
|  | ||||
| @ -8,9 +8,9 @@ import { HeaderTitle } from '../../common'; | ||||
| class StrategiesSectionComponent extends React.Component { | ||||
|     static propTypes = { | ||||
|         strategies: PropTypes.array.isRequired, | ||||
|         addStrategy: PropTypes.func.isRequired, | ||||
|         removeStrategy: PropTypes.func.isRequired, | ||||
|         updateStrategy: PropTypes.func.isRequired, | ||||
|         addStrategy: PropTypes.func, | ||||
|         removeStrategy: PropTypes.func, | ||||
|         updateStrategy: PropTypes.func, | ||||
|         fetchStrategies: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
| @ -25,7 +25,11 @@ class StrategiesSectionComponent extends React.Component { | ||||
| 
 | ||||
|         return ( | ||||
|             <div> | ||||
|                 <HeaderTitle title="Activation strategies" actions={<AddStrategy {...this.props} />} /> | ||||
|                 {this.props.addStrategy ? ( | ||||
|                     <HeaderTitle title="Activation strategies" actions={<AddStrategy {...this.props} />} /> | ||||
|                 ) : ( | ||||
|                     <span /> | ||||
|                 )} | ||||
|                 <StrategiesList {...this.props} /> | ||||
|             </div> | ||||
|         ); | ||||
|  | ||||
| @ -46,10 +46,10 @@ class StrategyConfigure extends React.Component { | ||||
|     /* eslint-enable */ | ||||
|     static propTypes = { | ||||
|         strategy: PropTypes.object.isRequired, | ||||
|         strategyDefinition: PropTypes.object.isRequired, | ||||
|         updateStrategy: PropTypes.func.isRequired, | ||||
|         removeStrategy: PropTypes.func.isRequired, | ||||
|         moveStrategy: PropTypes.func.isRequired, | ||||
|         strategyDefinition: PropTypes.object, | ||||
|         updateStrategy: PropTypes.func, | ||||
|         removeStrategy: PropTypes.func, | ||||
|         moveStrategy: PropTypes.func, | ||||
|         isDragging: PropTypes.bool.isRequired, | ||||
|         connectDragPreview: PropTypes.func.isRequired, | ||||
|         connectDragSource: PropTypes.func.isRequired, | ||||
| @ -170,7 +170,11 @@ class StrategyConfigure extends React.Component { | ||||
|                         <Link title="View strategy" to={`/strategies/view/${name}`} className={styles.editLink}> | ||||
|                             <Icon name="link" /> | ||||
|                         </Link> | ||||
|                         <IconButton title="Remove strategy from toggle" name="delete" onClick={this.handleRemove} /> | ||||
|                         {this.props.removeStrategy ? ( | ||||
|                             <IconButton title="Remove strategy from toggle" name="delete" onClick={this.handleRemove} /> | ||||
|                         ) : ( | ||||
|                             <span /> | ||||
|                         )} | ||||
|                         {connectDragSource( | ||||
|                             <span className={styles.reorderIcon}> | ||||
|                                 <Icon name="reorder" /> | ||||
|  | ||||
| @ -7,13 +7,15 @@ import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } f | ||||
| import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common'; | ||||
| import styles from './feature.scss'; | ||||
| 
 | ||||
| export default class FeatureListComponent extends React.PureComponent { | ||||
| export default class FeatureListComponent extends React.Component { | ||||
|     static propTypes = { | ||||
|         features: PropTypes.array.isRequired, | ||||
|         featureMetrics: PropTypes.object.isRequired, | ||||
|         fetchFeatureToggles: PropTypes.func.isRequired, | ||||
|         fetchFeatureToggles: PropTypes.func, | ||||
|         fetchArchive: PropTypes.func, | ||||
|         revive: PropTypes.func, | ||||
|         updateSetting: PropTypes.func.isRequired, | ||||
|         toggleFeature: PropTypes.func.isRequired, | ||||
|         toggleFeature: PropTypes.func, | ||||
|         settings: PropTypes.object, | ||||
|     }; | ||||
| 
 | ||||
| @ -22,7 +24,11 @@ export default class FeatureListComponent extends React.PureComponent { | ||||
|     }; | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         this.props.fetchFeatureToggles(); | ||||
|         if (this.props.fetchFeatureToggles) { | ||||
|             this.props.fetchFeatureToggles(); | ||||
|         } else { | ||||
|             this.props.fetchArchive(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     toggleMetrics() { | ||||
| @ -38,8 +44,10 @@ export default class FeatureListComponent extends React.PureComponent { | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const { features, toggleFeature, featureMetrics, settings } = this.props; | ||||
| 
 | ||||
|         const { features, toggleFeature, featureMetrics, settings, revive } = this.props; | ||||
|         features.forEach(e => { | ||||
|             e.reviveName = e.name; | ||||
|         }); | ||||
|         return ( | ||||
|             <div> | ||||
|                 <div className={styles.toolbar}> | ||||
| @ -108,6 +116,7 @@ export default class FeatureListComponent extends React.PureComponent { | ||||
|                                 metricsLastMinute={featureMetrics.lastMinute[feature.name]} | ||||
|                                 feature={feature} | ||||
|                                 toggleFeature={toggleFeature} | ||||
|                                 revive={revive} | ||||
|                             /> | ||||
|                         ))} | ||||
|                     </List> | ||||
|  | ||||
| @ -4,10 +4,10 @@ import { updateSettingForGroup } from '../../store/settings/actions'; | ||||
| 
 | ||||
| import FeatureListComponent from './list-component'; | ||||
| 
 | ||||
| const mapStateToProps = state => { | ||||
| export const mapStateToPropsConfigurable = isFeature => state => { | ||||
|     const featureMetrics = state.featureMetrics.toJS(); | ||||
|     const settings = state.settings.toJS().feature || {}; | ||||
|     let features = state.features.toJS(); | ||||
|     let features = isFeature ? state.features.toJS() : state.archive.get('list').toArray(); | ||||
|     if (settings.filter) { | ||||
|         try { | ||||
|             const regex = new RegExp(settings.filter, 'i'); | ||||
| @ -69,7 +69,7 @@ const mapStateToProps = state => { | ||||
|         settings, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| const mapStateToProps = mapStateToPropsConfigurable(true); | ||||
| const mapDispatchToProps = { | ||||
|     toggleFeature, | ||||
|     fetchFeatureToggles, | ||||
|  | ||||
| @ -6,6 +6,7 @@ import { hashHistory, Link } from 'react-router'; | ||||
| import HistoryComponent from '../history/history-list-toggle-container'; | ||||
| import MetricComponent from './metric-container'; | ||||
| import EditFeatureToggle from './form/form-update-feature-container'; | ||||
| import ViewFeatureToggle from './form/form-view-feature-container'; | ||||
| import { styles as commonStyles } from '../common'; | ||||
| 
 | ||||
| const TABS = { | ||||
| @ -15,24 +16,32 @@ const TABS = { | ||||
| }; | ||||
| 
 | ||||
| export default class ViewFeatureToggleComponent extends React.Component { | ||||
|     isFeatureView; | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|         this.isFeatureView = !!props.fetchFeatureToggles; | ||||
|     } | ||||
| 
 | ||||
|     static propTypes = { | ||||
|         activeTab: PropTypes.string.isRequired, | ||||
|         featureToggleName: PropTypes.string.isRequired, | ||||
|         features: PropTypes.array.isRequired, | ||||
|         toggleFeature: PropTypes.func.isRequired, | ||||
|         removeFeatureToggle: PropTypes.func.isRequired, | ||||
|         fetchFeatureToggles: PropTypes.func.isRequired, | ||||
|         editFeatureToggle: PropTypes.func.isRequired, | ||||
|         toggleFeature: PropTypes.func, | ||||
|         removeFeatureToggle: PropTypes.func, | ||||
|         revive: PropTypes.func, | ||||
|         fetchArchive: PropTypes.func, | ||||
|         fetchFeatureToggles: PropTypes.func, | ||||
|         editFeatureToggle: PropTypes.func, | ||||
|         featureToggle: PropTypes.object, | ||||
|     }; | ||||
| 
 | ||||
|     componentWillMount() { | ||||
|         if (this.props.features.length === 0) { | ||||
|             this.props.fetchFeatureToggles(); | ||||
|             if (this.isFeatureView) { | ||||
|                 this.props.fetchFeatureToggles(); | ||||
|             } else { | ||||
|                 this.props.fetchArchive(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -42,14 +51,18 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|         if (TABS[activeTab] === TABS.history) { | ||||
|             return <HistoryComponent toggleName={featureToggleName} />; | ||||
|         } else if (TABS[activeTab] === TABS.strategies) { | ||||
|             return <EditFeatureToggle featureToggle={featureToggle} />; | ||||
|             if (this.isFeatureView) { | ||||
|                 return <EditFeatureToggle featureToggle={featureToggle} />; | ||||
|             } | ||||
|             return <ViewFeatureToggle featureToggle={featureToggle} />; | ||||
|         } else { | ||||
|             return <MetricComponent featureToggle={featureToggle} />; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     goToTab(tabName, featureToggleName) { | ||||
|         hashHistory.push(`/features/${tabName}/${featureToggleName}`); | ||||
|         let view = this.props.fetchFeatureToggles ? 'features' : 'archive'; | ||||
|         hashHistory.push(`/${view}/${tabName}/${featureToggleName}`); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
| @ -57,6 +70,7 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|             featureToggle, | ||||
|             features, | ||||
|             activeTab, | ||||
|             revive, | ||||
|             // setValue, | ||||
|             featureToggleName, | ||||
|             toggleFeature, | ||||
| @ -94,6 +108,10 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|                 hashHistory.push('/features'); | ||||
|             } | ||||
|         }; | ||||
|         const reviveToggle = () => { | ||||
|             revive(featureToggle.name); | ||||
|             hashHistory.push('/features'); | ||||
|         }; | ||||
|         const updateFeatureToggle = () => { | ||||
|             let feature = { ...featureToggle }; | ||||
|             if (Array.isArray(feature.strategies)) { | ||||
| @ -113,16 +131,28 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|             <Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}> | ||||
|                 <CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle> | ||||
|                 <CardText> | ||||
|                     <Textfield | ||||
|                         floatingLabel | ||||
|                         style={{ width: '100%' }} | ||||
|                         rows={1} | ||||
|                         label="Description" | ||||
|                         required | ||||
|                         value={featureToggle.description} | ||||
|                         onChange={v => setValue('description', v)} | ||||
|                         onBlur={updateFeatureToggle} | ||||
|                     /> | ||||
|                     {this.isFeatureView ? ( | ||||
|                         <Textfield | ||||
|                             floatingLabel | ||||
|                             style={{ width: '100%' }} | ||||
|                             rows={1} | ||||
|                             label="Description" | ||||
|                             required | ||||
|                             value={featureToggle.description} | ||||
|                             onChange={v => setValue('description', v)} | ||||
|                             onBlur={updateFeatureToggle} | ||||
|                         /> | ||||
|                     ) : ( | ||||
|                         <Textfield | ||||
|                             disabled | ||||
|                             floatingLabel | ||||
|                             style={{ width: '100%' }} | ||||
|                             rows={1} | ||||
|                             label="Description" | ||||
|                             required | ||||
|                             value={featureToggle.description} | ||||
|                         /> | ||||
|                     )} | ||||
|                 </CardText> | ||||
| 
 | ||||
|                 <CardActions | ||||
| @ -135,6 +165,7 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|                 > | ||||
|                     <span style={{ paddingRight: '24px' }}> | ||||
|                         <Switch | ||||
|                             disabled={this.isFeatureView} | ||||
|                             ripple | ||||
|                             checked={featureToggle.enabled} | ||||
|                             onChange={() => toggleFeature(featureToggle.name)} | ||||
| @ -142,9 +173,16 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|                             {featureToggle.enabled ? 'Enabled' : 'Disabled'} | ||||
|                         </Switch> | ||||
|                     </span> | ||||
|                     <Button onClick={removeToggle} style={{ flexShrink: 0 }}> | ||||
|                         Archive | ||||
|                     </Button> | ||||
| 
 | ||||
|                     {this.isFeatureView ? ( | ||||
|                         <Button onClick={removeToggle} style={{ flexShrink: 0 }}> | ||||
|                             Archive | ||||
|                         </Button> | ||||
|                     ) : ( | ||||
|                         <Button onClick={reviveToggle} style={{ flexShrink: 0 }}> | ||||
|                             Revive | ||||
|                         </Button> | ||||
|                     )} | ||||
|                 </CardActions> | ||||
|                 <hr /> | ||||
|                 <Tabs | ||||
|  | ||||
| @ -23,6 +23,7 @@ import CreateStrategies from './page/strategies/create'; | ||||
| import HistoryPage from './page/history'; | ||||
| import HistoryTogglePage from './page/history/toggle'; | ||||
| import Archive from './page/archive'; | ||||
| import ShowArchive from './page/archive/show'; | ||||
| import Applications from './page/applications'; | ||||
| import ApplicationView from './page/applications/view'; | ||||
| 
 | ||||
| @ -68,7 +69,7 @@ ReactDOM.render( | ||||
|                 </Route> | ||||
|                 <Route pageTitle="Archived Toggles" link="/archive"> | ||||
|                     <Route pageTitle="Archived Toggles" path="/archive" component={Archive} /> | ||||
|                     <Route pageTitle=":name" path="/archive/:name" component={Archive} /> | ||||
|                     <Route pageTitle=":name" path="/archive/:activeTab/:name" component={ShowArchive} /> | ||||
|                 </Route> | ||||
| 
 | ||||
|                 <Route pageTitle="Applications" link="/applications"> | ||||
|  | ||||
							
								
								
									
										14
									
								
								frontend/src/page/archive/show.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/src/page/archive/show.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| import React, { PureComponent } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ViewFeatureToggle from './../../component/archive/view-container'; | ||||
| 
 | ||||
| export default class Features extends PureComponent { | ||||
|     static propTypes = { | ||||
|         params: PropTypes.object.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const { params } = this.props; | ||||
|         return <ViewFeatureToggle featureToggleName={params.name} activeTab={params.activeTab} />; | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user