mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Add stale marking of feature toggles
This commit is contained in:
		
							parent
							
								
									e7e0b78697
								
							
						
					
					
						commit
						f5ed3eaa1f
					
				| @ -140,8 +140,8 @@ IconLink.propTypes = { | ||||
|     icon: PropTypes.string, | ||||
| }; | ||||
| 
 | ||||
| export const DropdownButton = ({ label, id }) => ( | ||||
|     <Button id={id} className={styles.dropdownButton}> | ||||
| export const DropdownButton = ({ label, id, className }) => ( | ||||
|     <Button id={id} className={className || styles.dropdownButton}> | ||||
|         {label} | ||||
|         <Icon name="arrow_drop_down" className="mdl-color-text--grey-600" /> | ||||
|     </Button> | ||||
|  | ||||
| @ -47,7 +47,7 @@ exports[`renders correctly with one feature 1`] = ` | ||||
|     className="listItemStrategies hideLt920" | ||||
|   > | ||||
|     <react-mdl-Chip | ||||
|       className="mdl-color--blue-grey-100" | ||||
|       className="typeChip" | ||||
|     /> | ||||
|   </span> | ||||
|   <span /> | ||||
| @ -100,7 +100,7 @@ exports[`renders correctly with one feature without permission 1`] = ` | ||||
|     className="listItemStrategies hideLt920" | ||||
|   > | ||||
|     <react-mdl-Chip | ||||
|       className="mdl-color--blue-grey-100" | ||||
|       className="typeChip" | ||||
|     /> | ||||
|   </span> | ||||
|   <span /> | ||||
|  | ||||
| @ -10,6 +10,19 @@ exports[`renders correctly with one feature 1`] = ` | ||||
|     } | ||||
|   } | ||||
| > | ||||
|   <react-mdl-Chip | ||||
|     className="mdl-color--light-green-500 mdl-color-text--white mdl-shadow--2dp" | ||||
|     style={ | ||||
|       Object { | ||||
|         "position": "absolute", | ||||
|         "right": "4px", | ||||
|         "top": "4px", | ||||
|       } | ||||
|     } | ||||
|     title="Feature toggle is active." | ||||
|   > | ||||
|     Active | ||||
|   </react-mdl-Chip> | ||||
|   <react-mdl-CardTitle | ||||
|     style={ | ||||
|       Object { | ||||
| @ -73,6 +86,39 @@ exports[`renders correctly with one feature 1`] = ` | ||||
|       </react-mdl-Switch> | ||||
|     </span> | ||||
|     <div> | ||||
|       <span> | ||||
|         <react-mdl-Button | ||||
|           className="mdl-button" | ||||
|           id="update_status" | ||||
|         > | ||||
|           Status | ||||
|           <react-mdl-Icon | ||||
|             className="mdl-color-text--grey-600" | ||||
|             name="arrow_drop_down" | ||||
|           /> | ||||
|         </react-mdl-Button> | ||||
|         <react-mdl-Menu | ||||
|           onClick={[Function]} | ||||
|           style={ | ||||
|             Object { | ||||
|               "width": "168px", | ||||
|             } | ||||
|           } | ||||
|           target="update_status" | ||||
|         > | ||||
|           <react-mdl-MenuItem | ||||
|             data-target="active" | ||||
|             disabled={true} | ||||
|           > | ||||
|             Set toggle Active | ||||
|           </react-mdl-MenuItem> | ||||
|           <react-mdl-MenuItem | ||||
|             data-target="stale" | ||||
|           > | ||||
|             Mark toggle as Stale | ||||
|           </react-mdl-MenuItem> | ||||
|         </react-mdl-Menu> | ||||
|       </span> | ||||
|       <a | ||||
|         href="/features/copy/Another" | ||||
|         onClick={[Function]} | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl'; | ||||
| import Progress from './progress'; | ||||
| import { UPDATE_FEATURE } from '../../permissions'; | ||||
| import { calc, styles as commonStyles } from '../common'; | ||||
| import StatusComponent from './status-component'; | ||||
| 
 | ||||
| import styles from './feature.scss'; | ||||
| 
 | ||||
| @ -17,7 +18,7 @@ const Feature = ({ | ||||
|     revive, | ||||
|     hasPermission, | ||||
| }) => { | ||||
|     const { name, description, enabled, type } = feature; | ||||
|     const { name, description, enabled, type, stale } = feature; | ||||
|     const { showLastHour = false } = settings; | ||||
|     const isStale = showLastHour ? metricsLastHour.isFallback : metricsLastMinute.isFallback; | ||||
|     const percent = | ||||
| @ -25,7 +26,6 @@ const Feature = ({ | ||||
|         (showLastHour | ||||
|             ? calc(metricsLastHour.yes, metricsLastHour.yes + metricsLastHour.no, 0) | ||||
|             : calc(metricsLastMinute.yes, metricsLastMinute.yes + metricsLastMinute.no, 0)); | ||||
|     const typeChip = <Chip className="mdl-color--blue-grey-100">{type}</Chip>; | ||||
|     const featureUrl = toggleFeature === undefined ? `/archive/strategies/${name}` : `/features/strategies/${name}`; | ||||
|     return ( | ||||
|         <ListItem twoLine> | ||||
| @ -51,7 +51,10 @@ const Feature = ({ | ||||
|                     <span className={['mdl-list__item-sub-title', commonStyles.truncate].join(' ')}>{description}</span> | ||||
|                 </Link> | ||||
|             </span> | ||||
|             <span className={[styles.listItemStrategies, commonStyles.hideLt920].join(' ')}>{typeChip}</span> | ||||
|             <span className={[styles.listItemStrategies, commonStyles.hideLt920].join(' ')}> | ||||
|                 <StatusComponent stale={stale} showActive={false} /> | ||||
|                 <Chip className={styles.typeChip}>{type}</Chip> | ||||
|             </span> | ||||
|             {revive && hasPermission(UPDATE_FEATURE) ? ( | ||||
|                 <ListItemAction onClick={() => revive(feature.name)}> | ||||
|                     <Icon name="undo" /> | ||||
|  | ||||
| @ -35,6 +35,7 @@ | ||||
| } | ||||
| 
 | ||||
| .typeChip { | ||||
|     margin-left: 8px !important; | ||||
|     background: #d3c1ff; | ||||
|     margin: 0 8px !important; | ||||
|     box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); | ||||
|     background-color: #cfd8dc !important; | ||||
| } | ||||
							
								
								
									
										28
									
								
								frontend/src/component/feature/status-component.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								frontend/src/component/feature/status-component.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| import React from 'react'; | ||||
| import { Chip } from 'react-mdl'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| export default function StatusComponent({ stale, style, showActive = true }) { | ||||
|     if (!stale && !showActive) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     const className = stale | ||||
|         ? 'mdl-color--red mdl-color-text--white mdl-shadow--2dp' | ||||
|         : 'mdl-color--light-green-500 mdl-color-text--white mdl-shadow--2dp'; | ||||
| 
 | ||||
|     const title = stale ? 'Feature toggle is deprecated.' : 'Feature toggle is active.'; | ||||
|     const value = stale ? 'Stale' : 'Active'; | ||||
| 
 | ||||
|     return ( | ||||
|         <Chip style={style} title={title} className={className}> | ||||
|             {value} | ||||
|         </Chip> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| StatusComponent.propTypes = { | ||||
|     stale: PropTypes.bool.isRequired, | ||||
|     style: PropTypes.object, | ||||
|     showActive: PropTypes.bool, | ||||
| }; | ||||
							
								
								
									
										37
									
								
								frontend/src/component/feature/status-update-component.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/src/component/feature/status-update-component.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| import React from 'react'; | ||||
| import { Menu, MenuItem } from 'react-mdl'; | ||||
| import { DropdownButton } from '../common'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| export default function StatusUpdateComponent({ stale, updateStale }) { | ||||
|     function setStatus(field) { | ||||
|         if (field === 'active') { | ||||
|             updateStale(false); | ||||
|         } else { | ||||
|             updateStale(true); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <span> | ||||
|             <DropdownButton className="mdl-button" id="update_status" label="Status" /> | ||||
|             <Menu | ||||
|                 target="update_status" | ||||
|                 onClick={e => setStatus(e.target.getAttribute('data-target'))} | ||||
|                 style={{ width: '168px' }} | ||||
|             > | ||||
|                 <MenuItem disabled={!stale} data-target="active"> | ||||
|                     Set toggle Active | ||||
|                 </MenuItem> | ||||
|                 <MenuItem disabled={stale} data-target="stale"> | ||||
|                     Mark toggle as Stale | ||||
|                 </MenuItem> | ||||
|             </Menu> | ||||
|         </span> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| StatusUpdateComponent.propTypes = { | ||||
|     stale: PropTypes.bool.isRequired, | ||||
|     updateStale: PropTypes.func.isRequired, | ||||
| }; | ||||
| @ -12,6 +12,8 @@ import FeatureTypeSelect from './form/feature-type-select-container'; | ||||
| import UpdateDescriptionComponent from './form/update-description-component'; | ||||
| import { styles as commonStyles } from '../common'; | ||||
| import { CREATE_FEATURE, DELETE_FEATURE, UPDATE_FEATURE } from '../../permissions'; | ||||
| import StatusComponent from './status-component'; | ||||
| import StatusUpdateComponent from './status-update-component'; | ||||
| 
 | ||||
| const TABS = { | ||||
|     strategies: 0, | ||||
| @ -32,6 +34,7 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|         featureToggleName: PropTypes.string.isRequired, | ||||
|         features: PropTypes.array.isRequired, | ||||
|         toggleFeature: PropTypes.func, | ||||
|         setStale: PropTypes.func, | ||||
|         removeFeatureToggle: PropTypes.func, | ||||
|         revive: PropTypes.func, | ||||
|         fetchArchive: PropTypes.func, | ||||
| @ -159,8 +162,20 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
|             this.props.editFeatureToggle(feature); | ||||
|         }; | ||||
| 
 | ||||
|         const updateStale = stale => { | ||||
|             this.props.setStale(stale, featureToggleName); | ||||
|         }; | ||||
| 
 | ||||
|         return ( | ||||
|             <Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}> | ||||
|                 <StatusComponent | ||||
|                     stale={featureToggle.stale} | ||||
|                     style={{ | ||||
|                         position: 'absolute', | ||||
|                         right: '4px', | ||||
|                         top: '4px', | ||||
|                     }} | ||||
|                 /> | ||||
|                 <CardTitle style={{ wordBreak: 'break-all', paddingBottom: 0 }}>{featureToggle.name} </CardTitle> | ||||
|                 <CardText> | ||||
|                     <UpdateDescriptionComponent | ||||
| @ -201,6 +216,7 @@ export default class ViewFeatureToggleComponent extends React.Component { | ||||
| 
 | ||||
|                     {this.isFeatureView ? ( | ||||
|                         <div> | ||||
|                             <StatusUpdateComponent stale={featureToggle.stale} updateStale={updateStale} /> | ||||
|                             <Link | ||||
|                                 to={`/features/copy/${featureToggle.name}`} | ||||
|                                 title="Create new feature toggle by cloning configuration" | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { connect } from 'react-redux'; | ||||
| import { | ||||
|     fetchFeatureToggles, | ||||
|     toggleFeature, | ||||
|     setStale, | ||||
|     removeFeatureToggle, | ||||
|     editFeatureToggle, | ||||
| } from './../../store/feature-actions'; | ||||
| @ -20,6 +21,7 @@ export default connect( | ||||
|     { | ||||
|         fetchFeatureToggles, | ||||
|         toggleFeature, | ||||
|         setStale, | ||||
|         removeFeatureToggle, | ||||
|         editFeatureToggle, | ||||
|     } | ||||
|  | ||||
| @ -62,6 +62,17 @@ function toggle(enable, name) { | ||||
|     }).then(throwIfNotSuccess); | ||||
| } | ||||
| 
 | ||||
| function setStale(isStale, name) { | ||||
|     const action = isStale ? 'on' : 'off'; | ||||
|     return fetch(`${URI}/${name}/stale/${action}`, { | ||||
|         method: 'POST', | ||||
|         headers, | ||||
|         credentials: 'include', | ||||
|     }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| function remove(featureToggleName) { | ||||
|     return fetch(`${URI}/${featureToggleName}`, { | ||||
|         method: 'DELETE', | ||||
| @ -75,5 +86,6 @@ export default { | ||||
|     validate, | ||||
|     update, | ||||
|     toggle, | ||||
|     setStale, | ||||
|     remove, | ||||
| }; | ||||
|  | ||||
| @ -26,6 +26,13 @@ export function toggleFeature(enable, name) { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export function setStale(stale, name) { | ||||
|     debug('Set stale property on feature toggle ', name); | ||||
|     return dispatch => { | ||||
|         dispatch(requestSetStaleFeatureToggle(stale, name)); | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export function editFeatureToggle(featureToggle) { | ||||
|     debug('Update feature toggle ', featureToggle); | ||||
|     return dispatch => { | ||||
| @ -76,6 +83,21 @@ export function requestToggleFeatureToggle(enable, name) { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export function requestSetStaleFeatureToggle(stale, name) { | ||||
|     return dispatch => { | ||||
|         dispatch({ type: START_UPDATE_FEATURE_TOGGLE }); | ||||
| 
 | ||||
|         return api | ||||
|             .setStale(stale, name) | ||||
|             .then(featureToggle => { | ||||
|                 const info = `${name} marked as ${stale ? 'Stale' : 'Active'}.`; | ||||
|                 setTimeout(() => dispatch({ type: MUTE_ERROR, error: info }), 1000); | ||||
|                 dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle, info }); | ||||
|             }) | ||||
|             .catch(dispatchAndThrow(dispatch, ERROR_UPDATE_FEATURE_TOGGLE)); | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export function requestUpdateFeatureToggle(featureToggle) { | ||||
|     return dispatch => { | ||||
|         dispatch({ type: START_UPDATE_FEATURE_TOGGLE }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user